xref: /reactos/dll/win32/shimgvw/shimgvw.c (revision 10e7643c)
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 
528     pchFileTitle = PathFindFileNameW(node->FileName);
529     if (pchFileTitle && *pchFileTitle)
530     {
531         StringCbPrintfW(szTitleBuf, sizeof(szTitleBuf),
532                         L"%ls%ls%ls", szResStr, L" - ", pchFileTitle);
533         SetWindowTextW(hwnd, szTitleBuf);
534     }
535     else
536     {
537         SetWindowTextW(hwnd, szResStr);
538     }
539 
540     EnableToolBarButtons(image != NULL);
541 
542     /* Redraw the display window */
543     InvalidateRect(hwnd, NULL, FALSE);
544 }
545 
546 static SHIMGVW_FILENODE*
547 pBuildFileList(LPWSTR szFirstFile)
548 {
549     HANDLE hFindHandle;
550     WCHAR *extension;
551     WCHAR szSearchPath[MAX_PATH];
552     WCHAR szSearchMask[MAX_PATH];
553     WCHAR szFileTypes[MAX_PATH];
554     WIN32_FIND_DATAW findData;
555     SHIMGVW_FILENODE *currentNode = NULL;
556     SHIMGVW_FILENODE *root = NULL;
557     SHIMGVW_FILENODE *conductor = NULL;
558     ImageCodecInfo *codecInfo;
559     UINT num;
560     UINT size;
561     UINT j;
562 
563     StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
564     PathRemoveFileSpecW(szSearchPath);
565 
566     GdipGetImageDecodersSize(&num, &size);
567     codecInfo = malloc(size);
568     if (!codecInfo)
569     {
570         DPRINT1("malloc() failed in pLoadFileList()\n");
571         return NULL;
572     }
573 
574     GdipGetImageDecoders(num, size, codecInfo);
575 
576     root = malloc(sizeof(SHIMGVW_FILENODE));
577     if (!root)
578     {
579         DPRINT1("malloc() failed in pLoadFileList()\n");
580         free(codecInfo);
581         return NULL;
582     }
583 
584     conductor = root;
585 
586     for (j = 0; j < num; ++j)
587     {
588         StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
589 
590         extension = wcstok(szFileTypes, L";");
591         while (extension != NULL)
592         {
593             PathCombineW(szSearchMask, szSearchPath, extension);
594 
595             hFindHandle = FindFirstFileW(szSearchMask, &findData);
596             if (hFindHandle != INVALID_HANDLE_VALUE)
597             {
598                 do
599                 {
600                     PathCombineW(conductor->FileName, szSearchPath, findData.cFileName);
601 
602                     // compare the name of the requested file with the one currently found.
603                     // if the name matches, the current node is returned by the function.
604                     if (wcscmp(szFirstFile, conductor->FileName) == 0)
605                     {
606                         currentNode = conductor;
607                     }
608 
609                     conductor->Next = malloc(sizeof(SHIMGVW_FILENODE));
610 
611                     // if malloc fails, make circular what we have and return it
612                     if (!conductor->Next)
613                     {
614                         DPRINT1("malloc() failed in pLoadFileList()\n");
615 
616                         conductor->Next = root;
617                         root->Prev = conductor;
618 
619                         FindClose(hFindHandle);
620                         free(codecInfo);
621                         return conductor;
622                     }
623 
624                     conductor->Next->Prev = conductor;
625                     conductor = conductor->Next;
626                 }
627                 while (FindNextFileW(hFindHandle, &findData) != 0);
628 
629                 FindClose(hFindHandle);
630             }
631 
632             extension = wcstok(NULL, L";");
633         }
634     }
635 
636     // we now have a node too much in the list. In case the requested file was not found,
637     // we use this node to store the name of it, otherwise we free it.
638     if (currentNode == NULL)
639     {
640         StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile);
641         currentNode = conductor;
642     }
643     else
644     {
645         conductor = conductor->Prev;
646         free(conductor->Next);
647     }
648 
649     // link the last node with the first one to make the list circular
650     conductor->Next = root;
651     root->Prev = conductor;
652     conductor = currentNode;
653 
654     free(codecInfo);
655 
656     return conductor;
657 }
658 
659 static VOID
660 pFreeFileList(SHIMGVW_FILENODE *root)
661 {
662     SHIMGVW_FILENODE *conductor;
663 
664     if (!root)
665         return;
666 
667     root->Prev->Next = NULL;
668     root->Prev = NULL;
669 
670     while (root)
671     {
672         conductor = root;
673         root = conductor->Next;
674         free(conductor);
675     }
676 }
677 
678 static VOID
679 ImageView_UpdateWindow(HWND hwnd)
680 {
681     InvalidateRect(hwnd, NULL, FALSE);
682     UpdateWindow(hwnd);
683 }
684 
685 static HBRUSH CreateCheckerBoardBrush(HDC hdc)
686 {
687     static const CHAR pattern[] =
688         "\x28\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\x00\x00"
689         "\x00\x00\x80\x00\x00\x00\x23\x2E\x00\x00\x23\x2E\x00\x00\x10\x00\x00\x00"
690         "\x00\x00\x00\x00\x99\x99\x99\x00\xCC\xCC\xCC\x00\x00\x00\x00\x00\x00\x00"
691         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
692         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
693         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11"
694         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00"
695         "\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
696         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
697         "\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
698         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
699         "\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11"
700         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11";
701 
702     return CreateDIBPatternBrushPt(pattern, DIB_RGB_COLORS);
703 }
704 
705 static VOID
706 ImageView_DrawImage(HWND hwnd)
707 {
708     GpGraphics *graphics;
709     UINT ImageWidth, ImageHeight;
710     INT ZoomedWidth, ZoomedHeight, x, y;
711     PAINTSTRUCT ps;
712     RECT rect, margin;
713     HDC hdc;
714     HBRUSH white;
715     HGDIOBJ hbrOld;
716     UINT uFlags;
717     WCHAR szText[128];
718     HGDIOBJ hFontOld;
719 
720     hdc = BeginPaint(hwnd, &ps);
721     if (!hdc)
722     {
723         DPRINT1("BeginPaint() failed\n");
724         return;
725     }
726 
727     GdipCreateFromHDC(hdc, &graphics);
728     if (!graphics)
729     {
730         DPRINT1("GdipCreateFromHDC() failed\n");
731         return;
732     }
733 
734     GetClientRect(hwnd, &rect);
735     white = GetStockObject(WHITE_BRUSH);
736 
737     if (image == NULL)
738     {
739         FillRect(hdc, &rect, white);
740 
741         LoadStringW(hInstance, IDS_NOPREVIEW, szText, _countof(szText));
742 
743         SetTextColor(hdc, RGB(0, 0, 0));
744         SetBkMode(hdc, TRANSPARENT);
745 
746         hFontOld = SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
747         DrawTextW(hdc, szText, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER |
748                                           DT_NOPREFIX);
749         SelectObject(hdc, hFontOld);
750     }
751     else
752     {
753         GdipGetImageWidth(image, &ImageWidth);
754         GdipGetImageHeight(image, &ImageHeight);
755 
756         ZoomedWidth = (ImageWidth * ZoomPercents) / 100;
757         ZoomedHeight = (ImageHeight * ZoomPercents) / 100;
758 
759         x = (rect.right - ZoomedWidth) / 2;
760         y = (rect.bottom - ZoomedHeight) / 2;
761 
762         // Fill top part
763         margin = rect;
764         margin.bottom = y - 1;
765         FillRect(hdc, &margin, white);
766         // Fill bottom part
767         margin.top = y + ZoomedHeight + 1;
768         margin.bottom = rect.bottom;
769         FillRect(hdc, &margin, white);
770         // Fill left part
771         margin.top = y - 1;
772         margin.bottom = y + ZoomedHeight + 1;
773         margin.right = x - 1;
774         FillRect(hdc, &margin, white);
775         // Fill right part
776         margin.left = x + ZoomedWidth + 1;
777         margin.right = rect.right;
778         FillRect(hdc, &margin, white);
779 
780         DPRINT("x = %d, y = %d, ImageWidth = %u, ImageHeight = %u\n");
781         DPRINT("rect.right = %ld, rect.bottom = %ld\n", rect.right, rect.bottom);
782         DPRINT("ZoomPercents = %d, ZoomedWidth = %d, ZoomedHeight = %d\n",
783                ZoomPercents, ZoomedWidth, ZoomedWidth);
784 
785         if (ZoomPercents % 100 == 0)
786         {
787             GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
788             GdipSetSmoothingMode(graphics, SmoothingModeNone);
789         }
790         else
791         {
792             GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
793             GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
794         }
795 
796         uFlags = 0;
797         GdipGetImageFlags(image, &uFlags);
798 
799         if (uFlags & (ImageFlagsHasAlpha | ImageFlagsHasTranslucent))
800         {
801             HBRUSH hbr = CreateCheckerBoardBrush(hdc);
802             hbrOld = SelectObject(hdc, hbr);
803             Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
804             SelectObject(hdc, hbrOld);
805             DeleteObject(hbr);
806         }
807         else
808         {
809             hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
810             Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
811             SelectObject(hdc, hbrOld);
812         }
813 
814         GdipDrawImageRectI(graphics, image, x, y, ZoomedWidth, ZoomedHeight);
815     }
816     GdipDeleteGraphics(graphics);
817     EndPaint(hwnd, &ps);
818 }
819 
820 static BOOL
821 ImageView_LoadSettings(VOID)
822 {
823     HKEY hKey;
824     DWORD dwSize;
825     LONG nError;
826 
827     nError = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\ReactOS\\shimgvw", 0, KEY_READ, &hKey);
828     if (nError)
829         return FALSE;
830 
831     dwSize = sizeof(shiSettings);
832     nError = RegQueryValueExW(hKey, L"Settings", NULL, NULL, (LPBYTE)&shiSettings, &dwSize);
833     RegCloseKey(hKey);
834 
835     return !nError;
836 }
837 
838 static VOID
839 ImageView_SaveSettings(HWND hwnd)
840 {
841     WINDOWPLACEMENT wp;
842     HKEY hKey;
843 
844     ShowWindow(hwnd, SW_HIDE);
845     wp.length = sizeof(WINDOWPLACEMENT);
846     GetWindowPlacement(hwnd, &wp);
847 
848     shiSettings.Left = wp.rcNormalPosition.left;
849     shiSettings.Top  = wp.rcNormalPosition.top;
850     shiSettings.Right  = wp.rcNormalPosition.right;
851     shiSettings.Bottom = wp.rcNormalPosition.bottom;
852     shiSettings.Maximized = (IsZoomed(hwnd) || (wp.flags & WPF_RESTORETOMAXIMIZED));
853 
854     if (RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, NULL,
855         REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
856     {
857         RegSetValueEx(hKey, _T("Settings"), 0, REG_BINARY, (LPBYTE)&shiSettings, sizeof(SHIMGVW_SETTINGS));
858         RegCloseKey(hKey);
859     }
860 }
861 
862 static BOOL
863 ImageView_CreateToolBar(HWND hwnd)
864 {
865     hToolBar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
866                               WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | CCS_BOTTOM | TBSTYLE_TOOLTIPS,
867                               0, 0, 0, 0, hwnd,
868                               0, hInstance, NULL);
869     if (hToolBar != NULL)
870     {
871         HIMAGELIST hImageList;
872 
873         SendMessageW(hToolBar, TB_SETEXTENDEDSTYLE,
874                      0, TBSTYLE_EX_HIDECLIPPEDBUTTONS);
875 
876         SendMessageW(hToolBar, TB_BUTTONSTRUCTSIZE,
877                      sizeof(Buttons[0]), 0);
878 
879         hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1);
880         if (hImageList == NULL) return FALSE;
881 
882         for (UINT n = 0; n < _countof(BtnConfig); n++)
883         {
884             ImageList_AddMasked(hImageList, LoadImageW(hInstance, MAKEINTRESOURCEW(BtnConfig[n].idb), IMAGE_BITMAP,
885                                 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
886         }
887 
888         ImageList_Destroy((HIMAGELIST)SendMessageW(hToolBar, TB_SETIMAGELIST,
889                                                    0, (LPARAM)hImageList));
890 
891         SendMessageW(hToolBar, TB_ADDBUTTONS, _countof(Buttons), (LPARAM)Buttons);
892 
893         return TRUE;
894     }
895 
896     return FALSE;
897 }
898 
899 static void ImageView_OnTimer(HWND hwnd)
900 {
901     DWORD dwDelay;
902 
903     KillTimer(hwnd, ANIME_TIMER_ID);
904     InvalidateRect(hwnd, NULL, FALSE);
905 
906     if (Anime_Step(&dwDelay))
907     {
908         SetTimer(hwnd, ANIME_TIMER_ID, dwDelay, NULL);
909     }
910 }
911 
912 LRESULT CALLBACK
913 ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
914 {
915     switch (Message)
916     {
917         case WM_PAINT:
918         {
919             ImageView_DrawImage(hwnd);
920             return 0L;
921         }
922         case WM_TIMER:
923         {
924             if (wParam == ANIME_TIMER_ID)
925             {
926                 ImageView_OnTimer(hwnd);
927                 return 0;
928             }
929             break;
930         }
931     }
932     return CallWindowProcW(PrevProc, hwnd, Message, wParam, lParam);
933 }
934 
935 static VOID
936 ImageView_InitControls(HWND hwnd)
937 {
938     MoveWindow(hwnd, shiSettings.Left, shiSettings.Top,
939                shiSettings.Right - shiSettings.Left,
940                shiSettings.Bottom - shiSettings.Top, TRUE);
941 
942     if (shiSettings.Maximized) ShowWindow(hwnd, SW_MAXIMIZE);
943 
944     hDispWnd = CreateWindowExW(WS_EX_CLIENTEDGE, WC_STATIC, L"",
945                                WS_CHILD | WS_VISIBLE,
946                                0, 0, 0, 0, hwnd, NULL, hInstance, NULL);
947 
948     SetClassLongPtr(hDispWnd, GCL_STYLE, CS_HREDRAW | CS_VREDRAW);
949     PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) ImageView_DispWndProc);
950 
951     ImageView_CreateToolBar(hwnd);
952 }
953 
954 static VOID
955 ImageView_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
956 {
957     if (zDelta != 0)
958     {
959         ZoomInOrOut(zDelta > 0);
960     }
961 }
962 
963 static VOID
964 ImageView_OnSize(HWND hwnd, UINT state, INT cx, INT cy)
965 {
966     RECT rc;
967 
968     SendMessageW(hToolBar, TB_AUTOSIZE, 0, 0);
969 
970     GetWindowRect(hToolBar, &rc);
971 
972     MoveWindow(hDispWnd, 0, 0, cx, cy - (rc.bottom - rc.top), TRUE);
973 
974     /* is it maximized or restored? */
975     if (state == SIZE_MAXIMIZED || state == SIZE_RESTORED)
976     {
977         /* reset zoom */
978         ResetZoom();
979     }
980 }
981 
982 static LRESULT
983 ImageView_Delete(HWND hwnd)
984 {
985     DPRINT1("ImageView_Delete: unimplemented.\n");
986     return 0;
987 }
988 
989 static LRESULT
990 ImageView_Modify(HWND hwnd)
991 {
992     int nChars = GetFullPathNameW(currentFile->FileName, 0, NULL, NULL);
993     LPWSTR pszPathName;
994     SHELLEXECUTEINFOW sei;
995 
996     if (!nChars)
997     {
998         DPRINT1("ImageView_Modify: failed to get full path name.\n");
999         return 1;
1000     }
1001 
1002     pszPathName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, nChars * sizeof(WCHAR));
1003     if (pszPathName == NULL)
1004     {
1005         DPRINT1("HeapAlloc() failed in ImageView_Modify()\n");
1006         return 1;
1007     }
1008 
1009     GetFullPathNameW(currentFile->FileName, nChars, pszPathName, NULL);
1010 
1011     sei.cbSize = sizeof(sei);
1012     sei.fMask = 0;
1013     sei.hwnd = NULL;
1014     sei.lpVerb = L"edit";
1015     sei.lpFile = pszPathName;
1016     sei.lpParameters = NULL;
1017     sei.lpDirectory = NULL;
1018     sei.nShow = SW_SHOWNORMAL;
1019     sei.hInstApp = NULL;
1020 
1021     if (!ShellExecuteExW(&sei))
1022     {
1023         DPRINT1("ImageView_Modify: ShellExecuteExW() failed with code %08X\n", (int)GetLastError());
1024     }
1025 
1026     HeapFree(GetProcessHeap(), 0, pszPathName);
1027 
1028     return 0;
1029 }
1030 
1031 LRESULT CALLBACK
1032 ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
1033 {
1034     switch (Message)
1035     {
1036         case WM_CREATE:
1037         {
1038             ImageView_InitControls(hwnd);
1039             return 0L;
1040         }
1041 
1042         case WM_COMMAND:
1043         {
1044             switch (LOWORD(wParam))
1045             {
1046                 case IDC_PREV_PIC:
1047                     currentFile = currentFile->Prev;
1048                     pLoadImageFromNode(currentFile, hwnd);
1049                     break;
1050 
1051                 case IDC_NEXT_PIC:
1052                     currentFile = currentFile->Next;
1053                     pLoadImageFromNode(currentFile, hwnd);
1054                     break;
1055 
1056                 case IDC_BEST_FIT:
1057                     DPRINT1("IDC_BEST_FIT unimplemented\n");
1058                     break;
1059 
1060                 case IDC_REAL_SIZE:
1061                     UpdateZoom(100);
1062                     return 0;
1063 
1064                 case IDC_SLIDE_SHOW:
1065                     DPRINT1("IDC_SLIDE_SHOW unimplemented\n");
1066                     break;
1067 
1068                 case IDC_ZOOM_IN:
1069                     ZoomInOrOut(TRUE);
1070                     break;
1071 
1072                 case IDC_ZOOM_OUT:
1073                     ZoomInOrOut(FALSE);
1074                     break;
1075 
1076                 case IDC_SAVEAS:
1077                     pSaveImageAs(hwnd);
1078                     break;
1079 
1080                 case IDC_PRINT:
1081                     pPrintImage(hwnd);
1082                     break;
1083 
1084                 case IDC_ROT_CLOCKW:
1085                     if (image)
1086                     {
1087                         GdipImageRotateFlip(image, Rotate270FlipNone);
1088                         ImageView_UpdateWindow(hwnd);
1089                     }
1090                     break;
1091 
1092                 case IDC_ROT_COUNCW:
1093                     if (image)
1094                     {
1095                         GdipImageRotateFlip(image, Rotate90FlipNone);
1096                         ImageView_UpdateWindow(hwnd);
1097                     }
1098                     break;
1099 
1100                 case IDC_DELETE:
1101                     return ImageView_Delete(hwnd);
1102 
1103                 case IDC_MODIFY:
1104                     return ImageView_Modify(hwnd);
1105             }
1106         }
1107         break;
1108 
1109         case WM_MOUSEWHEEL:
1110             ImageView_OnMouseWheel(hwnd,
1111                 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
1112                 (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
1113             break;
1114 
1115         case WM_NOTIFY:
1116         {
1117             LPNMHDR pnmhdr = (LPNMHDR)lParam;
1118 
1119             switch (pnmhdr->code)
1120             {
1121                 case TTN_GETDISPINFO:
1122                 {
1123                     LPTOOLTIPTEXTW lpttt;
1124 
1125                     lpttt = (LPTOOLTIPTEXTW)lParam;
1126                     lpttt->hinst = hInstance;
1127 
1128                     lpttt->lpszText = MAKEINTRESOURCEW(BtnConfig[lpttt->hdr.idFrom - IDC_TOOL_BASE].ids);
1129                     return 0;
1130                 }
1131             }
1132             break;
1133         }
1134         case WM_SIZING:
1135         {
1136             LPRECT pRect = (LPRECT)lParam;
1137             if (pRect->right-pRect->left < 350)
1138                 pRect->right = pRect->left + 350;
1139 
1140             if (pRect->bottom-pRect->top < 290)
1141                 pRect->bottom = pRect->top + 290;
1142             return TRUE;
1143         }
1144         case WM_SIZE:
1145         {
1146             ImageView_OnSize(hwnd, (UINT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1147             return 0;
1148         }
1149         case WM_DESTROY:
1150         {
1151             ImageView_SaveSettings(hwnd);
1152             SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) PrevProc);
1153             PostQuitMessage(0);
1154             break;
1155         }
1156     }
1157 
1158     return DefWindowProcW(hwnd, Message, wParam, lParam);
1159 }
1160 
1161 LONG WINAPI
1162 ImageView_CreateWindow(HWND hwnd, LPCWSTR szFileName)
1163 {
1164     struct GdiplusStartupInput gdiplusStartupInput;
1165     ULONG_PTR gdiplusToken;
1166     WNDCLASSW WndClass = {0};
1167     WCHAR szBuf[512];
1168     WCHAR szInitialFile[MAX_PATH];
1169     HWND hMainWnd;
1170     MSG msg;
1171     HACCEL hKbdAccel;
1172     HRESULT hComRes;
1173     INITCOMMONCONTROLSEX Icc = { .dwSize = sizeof(Icc), .dwICC = ICC_WIN95_CLASSES };
1174 
1175     InitCommonControlsEx(&Icc);
1176 
1177     hComRes = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1178     if (hComRes != S_OK && hComRes != S_FALSE)
1179     {
1180         DPRINT1("Warning, CoInitializeEx failed with code=%08X\n", (int)hComRes);
1181     }
1182 
1183     if (!ImageView_LoadSettings())
1184     {
1185         shiSettings.Maximized = FALSE;
1186         shiSettings.Left      = 0;
1187         shiSettings.Top       = 0;
1188         shiSettings.Right     = 520;
1189         shiSettings.Bottom    = 400;
1190     }
1191 
1192     // Initialize GDI+
1193     gdiplusStartupInput.GdiplusVersion              = 1;
1194     gdiplusStartupInput.DebugEventCallback          = NULL;
1195     gdiplusStartupInput.SuppressBackgroundThread    = FALSE;
1196     gdiplusStartupInput.SuppressExternalCodecs      = FALSE;
1197 
1198     GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1199     pLoadImage(szFileName);
1200 
1201     // Create the window
1202     WndClass.lpszClassName  = L"shimgvw_window";
1203     WndClass.lpfnWndProc    = ImageView_WndProc;
1204     WndClass.hInstance      = hInstance;
1205     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
1206     WndClass.hIcon          = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_APP_ICON));
1207     WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
1208     WndClass.hbrBackground  = NULL;   /* less flicker */
1209 
1210     if (!RegisterClassW(&WndClass)) return -1;
1211 
1212     LoadStringW(hInstance, IDS_APPTITLE, szBuf, _countof(szBuf));
1213     hMainWnd = CreateWindowExW(0, L"shimgvw_window", szBuf,
1214                                WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CAPTION,
1215                                CW_USEDEFAULT, CW_USEDEFAULT,
1216                                0, 0, NULL, NULL, hInstance, NULL);
1217 
1218     // make sure the path has no quotes on it
1219     StringCbCopyW(szInitialFile, sizeof(szInitialFile), szFileName);
1220     PathUnquoteSpacesW(szInitialFile);
1221 
1222     currentFile = pBuildFileList(szInitialFile);
1223     if (currentFile)
1224     {
1225         pLoadImageFromNode(currentFile, hMainWnd);
1226     }
1227 
1228     /* Create accelerator table for keystrokes */
1229     hKbdAccel = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(IDR_ACCELERATOR));
1230 
1231     // Show it
1232     ShowWindow(hMainWnd, SW_SHOW);
1233     UpdateWindow(hMainWnd);
1234 
1235     // Message Loop
1236     for (;;)
1237     {
1238         if (GetMessageW(&msg, NULL, 0, 0) <= 0)
1239             break;
1240 
1241         if (!TranslateAcceleratorW(hMainWnd, hKbdAccel, &msg))
1242         {
1243             TranslateMessage(&msg);
1244             DispatchMessageW(&msg);
1245         }
1246     }
1247 
1248     /* Destroy accelerator table */
1249     DestroyAcceleratorTable(hKbdAccel);
1250 
1251     pFreeFileList(currentFile);
1252 
1253     if (image)
1254     {
1255         GdipDisposeImage(image);
1256         image = NULL;
1257     }
1258 
1259     Anime_FreeInfo();
1260 
1261     GdiplusShutdown(gdiplusToken);
1262 
1263     /* Release COM resources */
1264     if (SUCCEEDED(hComRes))
1265         CoUninitialize();
1266 
1267     return -1;
1268 }
1269 
1270 VOID WINAPI
1271 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1272 {
1273     ImageView_CreateWindow(hwnd, path);
1274 }
1275 
1276 VOID WINAPI
1277 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1278 {
1279     ImageView_CreateWindow(hwnd, path);
1280 }
1281 
1282 VOID WINAPI
1283 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1284 {
1285     WCHAR szFile[MAX_PATH];
1286 
1287     if (MultiByteToWideChar(CP_ACP, 0, path, -1, szFile, _countof(szFile)))
1288     {
1289         ImageView_CreateWindow(hwnd, szFile);
1290     }
1291 }
1292 
1293 VOID WINAPI
1294 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1295 {
1296     DPRINT("ImageView_PrintTo() not implemented\n");
1297 }
1298 
1299 VOID WINAPI
1300 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1301 {
1302     DPRINT("ImageView_PrintToA() not implemented\n");
1303 }
1304 
1305 VOID WINAPI
1306 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1307 {
1308     DPRINT("ImageView_PrintToW() not implemented\n");
1309 }
1310 
1311 BOOL WINAPI
1312 DllMain(IN HINSTANCE hinstDLL,
1313         IN DWORD dwReason,
1314         IN LPVOID lpvReserved)
1315 {
1316     switch (dwReason)
1317     {
1318         case DLL_PROCESS_ATTACH:
1319         case DLL_THREAD_ATTACH:
1320             hInstance = hinstDLL;
1321             break;
1322     }
1323 
1324     return TRUE;
1325 }
1326