xref: /reactos/dll/win32/shimgvw/shimgvw.c (revision d6d1efe7)
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 <windowsx.h>
22 #include <objbase.h>
23 #include <commctrl.h>
24 #include <commdlg.h>
25 #include <gdiplus.h>
26 #include <tchar.h>
27 #include <strsafe.h>
28 #include <shlwapi.h>
29 
30 #define NDEBUG
31 #include <debug.h>
32 
33 #include "shimgvw.h"
34 
35 HINSTANCE hInstance;
36 SHIMGVW_SETTINGS shiSettings;
37 SHIMGVW_FILENODE *currentFile;
38 GpImage *image;
39 WNDPROC PrevProc = NULL;
40 
41 HWND hDispWnd, hToolBar;
42 
43 /* zooming */
44 #define MIN_ZOOM 10
45 #define MAX_ZOOM 1600
46 UINT ZoomPercents = 100;
47 static const UINT ZoomSteps[] =
48 {
49     10, 25, 50, 100, 200, 400, 800, 1600
50 };
51 
52 /* animation */
53 UINT            m_nFrameIndex = 0;
54 UINT            m_nFrameCount = 0;
55 UINT            m_nLoopIndex = 0;
56 UINT            m_nLoopCount = (UINT)-1;
57 PropertyItem   *m_pDelayItem = NULL;
58 
59 #define ANIME_TIMER_ID  9999
60 
61 static void Anime_FreeInfo(void)
62 {
63     if (m_pDelayItem)
64     {
65         free(m_pDelayItem);
66         m_pDelayItem = NULL;
67     }
68     m_nFrameIndex = 0;
69     m_nFrameCount = 0;
70     m_nLoopIndex = 0;
71     m_nLoopCount = (UINT)-1;
72 }
73 
74 static BOOL Anime_LoadInfo(void)
75 {
76     GUID *dims;
77     UINT nDimCount = 0;
78     UINT cbItem;
79     UINT result;
80     PropertyItem *pItem;
81 
82     Anime_FreeInfo();
83     KillTimer(hDispWnd, ANIME_TIMER_ID);
84 
85     if (!image)
86         return FALSE;
87 
88     GdipImageGetFrameDimensionsCount(image, &nDimCount);
89     if (nDimCount)
90     {
91         dims = (GUID *)calloc(nDimCount, sizeof(GUID));
92         if (dims)
93         {
94             GdipImageGetFrameDimensionsList(image, dims, nDimCount);
95             GdipImageGetFrameCount(image, dims, &result);
96             m_nFrameCount = result;
97             free(dims);
98         }
99     }
100 
101     result = 0;
102     GdipGetPropertyItemSize(image, PropertyTagFrameDelay, &result);
103     cbItem = result;
104     if (cbItem)
105     {
106         m_pDelayItem = (PropertyItem *)malloc(cbItem);
107         GdipGetPropertyItem(image, PropertyTagFrameDelay, cbItem, m_pDelayItem);
108     }
109 
110     result = 0;
111     GdipGetPropertyItemSize(image, PropertyTagLoopCount, &result);
112     cbItem = result;
113     if (cbItem)
114     {
115         pItem = (PropertyItem *)malloc(cbItem);
116         if (pItem)
117         {
118             if (GdipGetPropertyItem(image, PropertyTagLoopCount, cbItem, pItem) == Ok)
119             {
120                 m_nLoopCount = *(WORD *)pItem->value;
121             }
122             free(pItem);
123         }
124     }
125 
126     if (m_pDelayItem)
127     {
128         SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
129     }
130 
131     return m_pDelayItem != NULL;
132 }
133 
134 static void Anime_SetFrameIndex(UINT nFrameIndex)
135 {
136     if (nFrameIndex < m_nFrameCount)
137     {
138         GUID guid = FrameDimensionTime;
139         if (Ok != GdipImageSelectActiveFrame(image, &guid, nFrameIndex))
140         {
141             guid = FrameDimensionPage;
142             GdipImageSelectActiveFrame(image, &guid, nFrameIndex);
143         }
144     }
145     m_nFrameIndex = nFrameIndex;
146 }
147 
148 DWORD Anime_GetFrameDelay(UINT nFrameIndex)
149 {
150     if (nFrameIndex < m_nFrameCount && m_pDelayItem)
151     {
152         return ((DWORD *)m_pDelayItem->value)[m_nFrameIndex] * 10;
153     }
154     return 0;
155 }
156 
157 BOOL Anime_Step(DWORD *pdwDelay)
158 {
159     *pdwDelay = INFINITE;
160     if (m_nLoopCount == (UINT)-1)
161         return FALSE;
162 
163     if (m_nFrameIndex + 1 < m_nFrameCount)
164     {
165         *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
166         Anime_SetFrameIndex(m_nFrameIndex);
167         ++m_nFrameIndex;
168         return TRUE;
169     }
170 
171     if (m_nLoopCount == 0 || m_nLoopIndex < m_nLoopCount)
172     {
173         *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
174         Anime_SetFrameIndex(m_nFrameIndex);
175         m_nFrameIndex = 0;
176         ++m_nLoopIndex;
177         return TRUE;
178     }
179 
180     return FALSE;
181 }
182 
183 static void ZoomInOrOut(BOOL bZoomIn)
184 {
185     INT i;
186 
187     if (bZoomIn)    /* zoom in */
188     {
189         /* find next step */
190         for (i = 0; i < ARRAYSIZE(ZoomSteps); ++i)
191         {
192             if (ZoomPercents < ZoomSteps[i])
193                 break;
194         }
195         if (i == ARRAYSIZE(ZoomSteps))
196             ZoomPercents = MAX_ZOOM;
197         else
198             ZoomPercents = ZoomSteps[i];
199 
200         /* update tool bar buttons */
201         SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, TRUE);
202         if (ZoomPercents >= MAX_ZOOM)
203             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, FALSE);
204         else
205             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, TRUE);
206     }
207     else            /* zoom out */
208     {
209         /* find previous step */
210         for (i = ARRAYSIZE(ZoomSteps); i > 0; )
211         {
212             --i;
213             if (ZoomSteps[i] < ZoomPercents)
214                 break;
215         }
216         if (i < 0)
217             ZoomPercents = MIN_ZOOM;
218         else
219             ZoomPercents = ZoomSteps[i];
220 
221         /* update tool bar buttons */
222         SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, TRUE);
223         if (ZoomPercents <= MIN_ZOOM)
224             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, FALSE);
225         else
226             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, TRUE);
227     }
228 
229     /* redraw */
230     InvalidateRect(hDispWnd, NULL, TRUE);
231 }
232 
233 static void ResetZoom(void)
234 {
235     RECT Rect;
236     UINT ImageWidth, ImageHeight;
237 
238     /* get disp window size and image size */
239     GetClientRect(hDispWnd, &Rect);
240     GdipGetImageWidth(image, &ImageWidth);
241     GdipGetImageHeight(image, &ImageHeight);
242 
243     /* compare two aspect rates. same as
244        (ImageHeight / ImageWidth < Rect.bottom / Rect.right) in real */
245     if (ImageHeight * Rect.right < Rect.bottom * ImageWidth)
246     {
247         if (Rect.right < ImageWidth)
248         {
249             /* it's large, shrink it */
250             ZoomPercents = (Rect.right * 100) / ImageWidth;
251         }
252         else
253         {
254             /* it's small. show as original size */
255             ZoomPercents = 100;
256         }
257     }
258     else
259     {
260         if (Rect.bottom < ImageHeight)
261         {
262             /* it's large, shrink it */
263             ZoomPercents = (Rect.bottom * 100) / ImageHeight;
264         }
265         else
266         {
267             /* it's small. show as original size */
268             ZoomPercents = 100;
269         }
270     }
271 }
272 
273 /* ToolBar Buttons */
274 static const TBBUTTON Buttons [] =
275 {   /* iBitmap,     idCommand,   fsState,         fsStyle,     bReserved[2], dwData, iString */
276     {TBICON_PREV,   IDC_PREV,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
277     {TBICON_NEXT,   IDC_NEXT,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
278     {15,            0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0},
279     {TBICON_ZOOMP,  IDC_ZOOMP,   TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
280     {TBICON_ZOOMM,  IDC_ZOOMM,   TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
281     {15,            0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0},
282     {TBICON_ROT1,   IDC_ROT1,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
283     {TBICON_ROT2,   IDC_ROT2,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
284     {15,            0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0},
285     {TBICON_SAVE,   IDC_SAVE,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
286     {TBICON_PRINT,  IDC_PRINT,   TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
287 };
288 
289 static void pLoadImage(LPWSTR szOpenFileName)
290 {
291     /* check file presence */
292     if (GetFileAttributesW(szOpenFileName) == 0xFFFFFFFF)
293     {
294         DPRINT1("File %s not found!\n", szOpenFileName);
295         return;
296     }
297 
298     /* load now */
299     GdipLoadImageFromFile(szOpenFileName, &image);
300     if (!image)
301     {
302         DPRINT1("GdipLoadImageFromFile() failed\n");
303         return;
304     }
305     Anime_LoadInfo();
306 
307     /* reset zoom */
308     ResetZoom();
309 
310     /* redraw */
311     InvalidateRect(hDispWnd, NULL, TRUE);
312 }
313 
314 static void pSaveImageAs(HWND hwnd)
315 {
316     OPENFILENAMEW sfn;
317     ImageCodecInfo *codecInfo;
318     WCHAR szSaveFileName[MAX_PATH];
319     WCHAR *szFilterMask;
320     GUID rawFormat;
321     UINT num;
322     UINT size;
323     size_t sizeRemain;
324     UINT j;
325     WCHAR *c;
326 
327     GdipGetImageEncodersSize(&num, &size);
328     codecInfo = malloc(size);
329     if (!codecInfo)
330     {
331         DPRINT1("malloc() failed in pSaveImageAs()\n");
332         return;
333     }
334 
335     GdipGetImageEncoders(num, size, codecInfo);
336     GdipGetImageRawFormat(image, &rawFormat);
337 
338     sizeRemain = 0;
339 
340     for (j = 0; j < num; ++j)
341     {
342         // 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.
343         sizeRemain = sizeRemain + (((wcslen(codecInfo[j].FormatDescription) + (wcslen(codecInfo[j].FilenameExtension) * 2) + 5) * sizeof(WCHAR)));
344     }
345 
346     /* Add two more chars for the last terminator */
347     sizeRemain = sizeRemain + (sizeof(WCHAR) * 2);
348 
349     szFilterMask = malloc(sizeRemain);
350     if (!szFilterMask)
351     {
352         DPRINT1("cannot allocate memory for filter mask in pSaveImageAs()");
353         free(codecInfo);
354         return;
355     }
356 
357     ZeroMemory(szSaveFileName, sizeof(szSaveFileName));
358     ZeroMemory(szFilterMask, sizeRemain);
359     ZeroMemory(&sfn, sizeof(sfn));
360     sfn.lStructSize = sizeof(sfn);
361     sfn.hwndOwner   = hwnd;
362     sfn.hInstance   = hInstance;
363     sfn.lpstrFile   = szSaveFileName;
364     sfn.lpstrFilter = szFilterMask;
365     sfn.nMaxFile    = MAX_PATH;
366     sfn.Flags       = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
367 
368     c = szFilterMask;
369 
370     for (j = 0; j < num; ++j)
371     {
372         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls (%ls)", codecInfo[j].FormatDescription, codecInfo[j].FilenameExtension);
373 
374         /* Skip the NULL character */
375         c++;
376         sizeRemain -= sizeof(*c);
377 
378         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls", codecInfo[j].FilenameExtension);
379 
380         /* Skip the NULL character */
381         c++;
382         sizeRemain -= sizeof(*c);
383 
384         if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID) != FALSE)
385         {
386             sfn.nFilterIndex = j + 1;
387         }
388     }
389 
390     if (GetSaveFileNameW(&sfn))
391     {
392         if (m_pDelayItem)
393         {
394             /* save animation */
395             KillTimer(hDispWnd, ANIME_TIMER_ID);
396 
397             DPRINT1("FIXME: save animation\n");
398             if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
399             {
400                 DPRINT1("GdipSaveImageToFile() failed\n");
401             }
402 
403             SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
404         }
405         else
406         {
407             /* save non-animation */
408             if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
409             {
410                 DPRINT1("GdipSaveImageToFile() failed\n");
411             }
412         }
413     }
414 
415     free(szFilterMask);
416     free(codecInfo);
417 }
418 
419 static VOID
420 pLoadImageFromNode(SHIMGVW_FILENODE *node, HWND hwnd)
421 {
422     WCHAR szTitleBuf[800];
423     WCHAR szResStr[512];
424     WCHAR *c;
425 
426     if (node)
427     {
428         c = wcsrchr(node->FileName, '\\');
429         if (c)
430         {
431             c++;
432         }
433 
434         LoadStringW(hInstance, IDS_APPTITLE, szResStr, ARRAYSIZE(szResStr));
435         StringCbPrintfW(szTitleBuf, sizeof(szTitleBuf), L"%ls%ls%ls", szResStr, L" - ", c);
436         SetWindowTextW(hwnd, szTitleBuf);
437 
438         if (image)
439         {
440             GdipDisposeImage(image);
441         }
442 
443         pLoadImage(node->FileName);
444     }
445 }
446 
447 static SHIMGVW_FILENODE*
448 pBuildFileList(LPWSTR szFirstFile)
449 {
450     HANDLE hFindHandle;
451     WCHAR *extension;
452     WCHAR szSearchPath[MAX_PATH];
453     WCHAR szSearchMask[MAX_PATH];
454     WCHAR szFileTypes[MAX_PATH];
455     WIN32_FIND_DATAW findData;
456     SHIMGVW_FILENODE *currentNode = NULL;
457     SHIMGVW_FILENODE *root = NULL;
458     SHIMGVW_FILENODE *conductor = NULL;
459     ImageCodecInfo *codecInfo;
460     UINT num;
461     UINT size;
462     UINT j;
463 
464     StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
465     PathRemoveFileSpecW(szSearchPath);
466 
467     GdipGetImageDecodersSize(&num, &size);
468     codecInfo = malloc(size);
469     if (!codecInfo)
470     {
471         DPRINT1("malloc() failed in pLoadFileList()\n");
472         return NULL;
473     }
474 
475     GdipGetImageDecoders(num, size, codecInfo);
476 
477     root = malloc(sizeof(SHIMGVW_FILENODE));
478     if (!root)
479     {
480         DPRINT1("malloc() failed in pLoadFileList()\n");
481         free(codecInfo);
482         return NULL;
483     }
484 
485     conductor = root;
486 
487     for (j = 0; j < num; ++j)
488     {
489         StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
490 
491         extension = wcstok(szFileTypes, L";");
492         while (extension != NULL)
493         {
494             PathCombineW(szSearchMask, szSearchPath, extension);
495 
496             hFindHandle = FindFirstFileW(szSearchMask, &findData);
497             if (hFindHandle != INVALID_HANDLE_VALUE)
498             {
499                 do
500                 {
501                     PathCombineW(conductor->FileName, szSearchPath, findData.cFileName);
502 
503                     // compare the name of the requested file with the one currently found.
504                     // if the name matches, the current node is returned by the function.
505                     if (wcscmp(szFirstFile, conductor->FileName) == 0)
506                     {
507                         currentNode = conductor;
508                     }
509 
510                     conductor->Next = malloc(sizeof(SHIMGVW_FILENODE));
511 
512                     // if malloc fails, make circular what we have and return it
513                     if (!conductor->Next)
514                     {
515                         DPRINT1("malloc() failed in pLoadFileList()\n");
516 
517                         conductor->Next = root;
518                         root->Prev = conductor;
519 
520                         FindClose(hFindHandle);
521                         free(codecInfo);
522                         return conductor;
523                     }
524 
525                     conductor->Next->Prev = conductor;
526                     conductor = conductor->Next;
527                 }
528                 while (FindNextFileW(hFindHandle, &findData) != 0);
529 
530                 FindClose(hFindHandle);
531             }
532 
533             extension = wcstok(NULL, L";");
534         }
535     }
536 
537     // we now have a node too much in the list. In case the requested file was not found,
538     // we use this node to store the name of it, otherwise we free it.
539     if (currentNode == NULL)
540     {
541         StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile);
542         currentNode = conductor;
543     }
544     else
545     {
546         conductor = conductor->Prev;
547         free(conductor->Next);
548     }
549 
550     // link the last node with the first one to make the list circular
551     conductor->Next = root;
552     root->Prev = conductor;
553     conductor = currentNode;
554 
555     free(codecInfo);
556 
557     return conductor;
558 }
559 
560 static VOID
561 pFreeFileList(SHIMGVW_FILENODE *root)
562 {
563     SHIMGVW_FILENODE *conductor;
564 
565     root->Prev->Next = NULL;
566     root->Prev = NULL;
567 
568     while (root)
569     {
570         conductor = root;
571         root = conductor->Next;
572         free(conductor);
573     }
574 }
575 
576 static VOID
577 ImageView_UpdateWindow(HWND hwnd)
578 {
579     InvalidateRect(hwnd, NULL, FALSE);
580     UpdateWindow(hwnd);
581 }
582 
583 static VOID
584 ImageView_DrawImage(HWND hwnd)
585 {
586     GpGraphics *graphics;
587     UINT ImageWidth, ImageHeight;
588     INT ZoomedWidth, ZoomedHeight, x, y;
589     PAINTSTRUCT ps;
590     RECT rect, margin;
591     HDC hdc;
592     HBRUSH white;
593     HGDIOBJ hbrOld;
594 
595     hdc = BeginPaint(hwnd, &ps);
596     if (!hdc)
597     {
598         DPRINT1("BeginPaint() failed\n");
599         return;
600     }
601 
602     GdipCreateFromHDC(hdc, &graphics);
603     if (!graphics)
604     {
605         DPRINT1("GdipCreateFromHDC() failed\n");
606         return;
607     }
608 
609     GdipGetImageWidth(image, &ImageWidth);
610     GdipGetImageHeight(image, &ImageHeight);
611 
612     if (GetClientRect(hwnd, &rect))
613     {
614         ZoomedWidth = (ImageWidth * ZoomPercents) / 100;
615         ZoomedHeight = (ImageHeight * ZoomPercents) / 100;
616 
617         x = (rect.right - ZoomedWidth) / 2;
618         y = (rect.bottom - ZoomedHeight) / 2;
619 
620         white = GetStockObject(WHITE_BRUSH);
621         // Fill top part
622         margin = rect;
623         margin.bottom = y - 1;
624         FillRect(hdc, &margin, white);
625         // Fill bottom part
626         margin.top = y + ZoomedHeight + 1;
627         margin.bottom = rect.bottom;
628         FillRect(hdc, &margin, white);
629         // Fill left part
630         margin.top = y - 1;
631         margin.bottom = y + ZoomedHeight + 1;
632         margin.right = x - 1;
633         FillRect(hdc, &margin, white);
634         // Fill right part
635         margin.left = x + ZoomedWidth + 1;
636         margin.right = rect.right;
637         FillRect(hdc, &margin, white);
638 
639         DPRINT("x = %d, y = %d, ImageWidth = %u, ImageHeight = %u\n");
640         DPRINT("rect.right = %ld, rect.bottom = %ld\n", rect.right, rect.bottom);
641         DPRINT("ZoomPercents = %d, ZoomedWidth = %d, ZoomedHeight = %d\n",
642                ZoomPercents, ZoomedWidth, ZoomedWidth);
643 
644         if (ZoomPercents % 100 == 0)
645         {
646             GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
647             GdipSetSmoothingMode(graphics, SmoothingModeNone);
648         }
649         else
650         {
651             GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
652             GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
653         }
654 
655         hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
656         Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
657         SelectObject(hdc, hbrOld);
658         GdipDrawImageRectI(graphics, image, x, y, ZoomedWidth, ZoomedHeight);
659     }
660     GdipDeleteGraphics(graphics);
661     EndPaint(hwnd, &ps);
662 }
663 
664 static BOOL
665 ImageView_LoadSettings()
666 {
667     HKEY hKey;
668     DWORD dwSize;
669 
670     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
671     {
672         dwSize = sizeof(SHIMGVW_SETTINGS);
673         if (RegQueryValueEx(hKey, _T("Settings"), NULL, NULL, (LPBYTE)&shiSettings, &dwSize) == ERROR_SUCCESS)
674         {
675             RegCloseKey(hKey);
676             return TRUE;
677         }
678 
679         RegCloseKey(hKey);
680     }
681 
682     return FALSE;
683 }
684 
685 static VOID
686 ImageView_SaveSettings(HWND hwnd)
687 {
688     WINDOWPLACEMENT wp;
689     HKEY hKey;
690 
691     ShowWindow(hwnd, SW_HIDE);
692     wp.length = sizeof(WINDOWPLACEMENT);
693     GetWindowPlacement(hwnd, &wp);
694 
695     shiSettings.Left = wp.rcNormalPosition.left;
696     shiSettings.Top  = wp.rcNormalPosition.top;
697     shiSettings.Right  = wp.rcNormalPosition.right;
698     shiSettings.Bottom = wp.rcNormalPosition.bottom;
699     shiSettings.Maximized = (IsZoomed(hwnd) || (wp.flags & WPF_RESTORETOMAXIMIZED));
700 
701     if (RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, NULL,
702         REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
703     {
704         RegSetValueEx(hKey, _T("Settings"), 0, REG_BINARY, (LPBYTE)&shiSettings, sizeof(SHIMGVW_SETTINGS));
705         RegCloseKey(hKey);
706     }
707 }
708 
709 static BOOL
710 ImageView_CreateToolBar(HWND hwnd)
711 {
712     INT numButtons = sizeof(Buttons) / sizeof(Buttons[0]);
713 
714     hToolBar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
715                               WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | CCS_BOTTOM | TBSTYLE_TOOLTIPS,
716                               0, 0, 0, 0, hwnd,
717                               0, hInstance, NULL);
718     if(hToolBar != NULL)
719     {
720         HIMAGELIST hImageList;
721 
722         SendMessage(hToolBar, TB_SETEXTENDEDSTYLE,
723                     0, TBSTYLE_EX_HIDECLIPPEDBUTTONS);
724 
725         SendMessage(hToolBar, TB_BUTTONSTRUCTSIZE,
726                     sizeof(Buttons[0]), 0);
727 
728         hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1);
729 
730         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_PREVICON), IMAGE_BITMAP,
731                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
732 
733         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_NEXTICON), IMAGE_BITMAP,
734                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
735 
736         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ZOOMPICON), IMAGE_BITMAP,
737                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
738 
739         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ZOOMMICON), IMAGE_BITMAP,
740                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
741 
742         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_SAVEICON), IMAGE_BITMAP,
743                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
744 
745         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_PRINTICON), IMAGE_BITMAP,
746                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
747 
748         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ROT1ICON), IMAGE_BITMAP,
749                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
750 
751         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ROT2ICON), IMAGE_BITMAP,
752                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
753 
754         if (hImageList == NULL) return FALSE;
755 
756         ImageList_Destroy((HIMAGELIST)SendMessage(hToolBar, TB_SETIMAGELIST,
757                                                   0, (LPARAM)hImageList));
758 
759         SendMessage(hToolBar, TB_ADDBUTTONS,
760                     numButtons, (LPARAM)Buttons);
761 
762         return TRUE;
763     }
764 
765     return FALSE;
766 }
767 
768 static void ImageView_OnTimer(HWND hwnd)
769 {
770     DWORD dwDelay;
771 
772     KillTimer(hwnd, ANIME_TIMER_ID);
773     InvalidateRect(hwnd, NULL, FALSE);
774 
775     if (Anime_Step(&dwDelay))
776     {
777         SetTimer(hwnd, ANIME_TIMER_ID, dwDelay, NULL);
778     }
779 }
780 
781 LRESULT CALLBACK
782 ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
783 {
784     switch (Message)
785     {
786         case WM_PAINT:
787         {
788             ImageView_DrawImage(hwnd);
789             return 0L;
790         }
791         case WM_TIMER:
792         {
793             if (wParam == ANIME_TIMER_ID)
794             {
795                 ImageView_OnTimer(hwnd);
796                 return 0;
797             }
798             break;
799         }
800     }
801     return CallWindowProc(PrevProc, hwnd, Message, wParam, lParam);
802 }
803 
804 static VOID
805 ImageView_InitControls(HWND hwnd)
806 {
807     MoveWindow(hwnd, shiSettings.Left, shiSettings.Top,
808                shiSettings.Right - shiSettings.Left,
809                shiSettings.Bottom - shiSettings.Top, TRUE);
810 
811     if (shiSettings.Maximized) ShowWindow(hwnd, SW_MAXIMIZE);
812 
813     hDispWnd = CreateWindowEx(0, WC_STATIC, _T(""),
814                               WS_CHILD | WS_VISIBLE,
815                               0, 0, 0, 0, hwnd, NULL, hInstance, NULL);
816 
817     SetClassLongPtr(hDispWnd, GCL_STYLE, CS_HREDRAW | CS_VREDRAW);
818     PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) ImageView_DispWndProc);
819 
820     ImageView_CreateToolBar(hwnd);
821 }
822 
823 static VOID
824 ImageView_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
825 {
826     if (zDelta != 0)
827     {
828         ZoomInOrOut(zDelta > 0);
829     }
830 }
831 
832 LRESULT CALLBACK
833 ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
834 {
835     switch (Message)
836     {
837         case WM_CREATE:
838         {
839             ImageView_InitControls(hwnd);
840             return 0L;
841         }
842 
843         case WM_KEYDOWN:
844             switch (LOWORD(wParam))
845             {
846                 case VK_LEFT:
847                     PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_PREV, BN_CLICKED), (LPARAM)NULL);
848                     break;
849 
850                 case VK_RIGHT:
851                     PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_NEXT, BN_CLICKED), (LPARAM)NULL);
852                     break;
853             }
854             break;
855 
856         case WM_COMMAND:
857         {
858             switch (wParam)
859             {
860                 case IDC_PREV:
861                 {
862                     currentFile = currentFile->Prev;
863                     pLoadImageFromNode(currentFile, hwnd);
864                 }
865 
866                 break;
867                 case IDC_NEXT:
868                 {
869                     currentFile = currentFile->Next;
870                     pLoadImageFromNode(currentFile, hwnd);
871                 }
872 
873                 break;
874                 case IDC_ZOOMP:
875                 {
876                     ZoomInOrOut(TRUE);
877                 }
878                 break;
879                 case IDC_ZOOMM:
880                 {
881                     ZoomInOrOut(FALSE);
882                 }
883                 break;
884                 case IDC_SAVE:
885                     pSaveImageAs(hwnd);
886 
887                 break;
888                 case IDC_PRINT:
889 
890                 break;
891                 case IDC_ROT1:
892                 {
893                     GdipImageRotateFlip(image, Rotate270FlipNone);
894                     ImageView_UpdateWindow(hwnd);
895                 }
896 
897                 break;
898                 case IDC_ROT2:
899                 {
900                     GdipImageRotateFlip(image, Rotate90FlipNone);
901                     ImageView_UpdateWindow(hwnd);
902                 }
903 
904                 break;
905             }
906         }
907         break;
908 
909         case WM_MOUSEWHEEL:
910             ImageView_OnMouseWheel(hwnd,
911                 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
912                 (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
913             break;
914 
915         case WM_NOTIFY:
916         {
917             LPNMHDR pnmhdr = (LPNMHDR)lParam;
918 
919             switch (pnmhdr->code)
920             {
921                 case TTN_GETDISPINFO:
922                 {
923                     LPTOOLTIPTEXT lpttt;
924                     UINT idButton;
925 
926                     lpttt = (LPTOOLTIPTEXT)lParam;
927                     idButton = (UINT)lpttt->hdr.idFrom;
928                     lpttt->hinst = hInstance;
929 
930                     switch (idButton)
931                     {
932                         case IDC_PREV:
933                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_PREV_PIC);
934                         break;
935                         case IDC_NEXT:
936                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_NEXT_PIC);
937                         break;
938                         case IDC_ZOOMP:
939                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ZOOM_IN);
940                         break;
941                         case IDC_ZOOMM:
942                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ZOOM_OUT);
943                         break;
944                         case IDC_SAVE:
945                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_SAVEAS);
946                         break;
947                         case IDC_PRINT:
948                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_PRINT);
949                         break;
950                         case IDC_ROT1:
951                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ROT_COUNCW);
952                         break;
953                         case IDC_ROT2:
954                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ROT_CLOCKW);
955                         break;
956                     }
957                     return TRUE;
958                 }
959             }
960             break;
961         }
962         case WM_SIZING:
963         {
964             LPRECT pRect = (LPRECT)lParam;
965             if (pRect->right-pRect->left < 350)
966                 pRect->right = pRect->left + 350;
967 
968             if (pRect->bottom-pRect->top < 290)
969                 pRect->bottom = pRect->top + 290;
970             return TRUE;
971         }
972         case WM_SIZE:
973         {
974             RECT rc;
975             SendMessage(hToolBar, TB_AUTOSIZE, 0, 0);
976             GetWindowRect(hToolBar, &rc);
977             MoveWindow(hDispWnd, 1, 1, LOWORD(lParam) - 1, HIWORD(lParam) - (rc.bottom - rc.top) - 1, TRUE);
978             /* is it maximized or restored? */
979             if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)
980             {
981                 /* reset zoom */
982                 ResetZoom();
983             }
984             return 0L;
985         }
986         case WM_DESTROY:
987         {
988             ImageView_SaveSettings(hwnd);
989             SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) PrevProc);
990             PostQuitMessage(0);
991             break;
992         }
993     }
994 
995     return DefWindowProc(hwnd, Message, wParam, lParam);
996 }
997 
998 LONG WINAPI
999 ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
1000 {
1001     struct GdiplusStartupInput gdiplusStartupInput;
1002     ULONG_PTR gdiplusToken;
1003     WNDCLASS WndClass = {0};
1004     TCHAR szBuf[512];
1005     WCHAR szInitialFile[MAX_PATH];
1006     HWND hMainWnd;
1007     MSG msg;
1008 
1009     if (!ImageView_LoadSettings())
1010     {
1011         shiSettings.Maximized = FALSE;
1012         shiSettings.Left      = 0;
1013         shiSettings.Top       = 0;
1014         shiSettings.Right     = 520;
1015         shiSettings.Bottom    = 400;
1016     }
1017 
1018     // Initialize GDI+
1019     gdiplusStartupInput.GdiplusVersion              = 1;
1020     gdiplusStartupInput.DebugEventCallback          = NULL;
1021     gdiplusStartupInput.SuppressBackgroundThread    = FALSE;
1022     gdiplusStartupInput.SuppressExternalCodecs      = FALSE;
1023 
1024     GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1025     pLoadImage(szFileName);
1026 
1027     // Create the window
1028     WndClass.lpszClassName  = _T("shimgvw_window");
1029     WndClass.lpfnWndProc    = ImageView_WndProc;
1030     WndClass.hInstance      = hInstance;
1031     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
1032     WndClass.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPICON));
1033     WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
1034     WndClass.hbrBackground  = NULL;   /* less flicker */
1035 
1036     if (!RegisterClass(&WndClass)) return -1;
1037 
1038     LoadString(hInstance, IDS_APPTITLE, szBuf, sizeof(szBuf) / sizeof(TCHAR));
1039     hMainWnd = CreateWindow(_T("shimgvw_window"), szBuf,
1040                             WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CAPTION,
1041                             CW_USEDEFAULT, CW_USEDEFAULT,
1042                             0, 0, NULL, NULL, hInstance, NULL);
1043 
1044     // make sure the path has no quotes on it
1045     wcscpy(szInitialFile, szFileName);
1046     PathUnquoteSpacesW(szInitialFile);
1047 
1048     currentFile = pBuildFileList(szInitialFile);
1049     if (currentFile)
1050     {
1051         pLoadImageFromNode(currentFile, hMainWnd);
1052     }
1053 
1054     // Show it
1055     ShowWindow(hMainWnd, SW_SHOW);
1056     UpdateWindow(hMainWnd);
1057 
1058     // Message Loop
1059     while(GetMessage(&msg,NULL,0,0))
1060     {
1061         TranslateMessage(&msg);
1062         DispatchMessageW(&msg);
1063     }
1064 
1065     pFreeFileList(currentFile);
1066 
1067     if (image)
1068         GdipDisposeImage(image);
1069 
1070     Anime_FreeInfo();
1071 
1072     GdiplusShutdown(gdiplusToken);
1073     return -1;
1074 }
1075 
1076 VOID WINAPI
1077 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1078 {
1079     ImageView_CreateWindow(hwnd, (LPWSTR)path);
1080 }
1081 
1082 VOID WINAPI
1083 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1084 {
1085     ImageView_CreateWindow(hwnd, (LPWSTR)path);
1086 }
1087 
1088 VOID WINAPI
1089 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1090 {
1091     WCHAR szFile[MAX_PATH];
1092 
1093     if (MultiByteToWideChar(CP_ACP, 0, (char*)path, strlen((char*)path)+1, szFile, MAX_PATH))
1094     {
1095         ImageView_CreateWindow(hwnd, (LPWSTR)szFile);
1096     }
1097 }
1098 
1099 VOID WINAPI
1100 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1101 {
1102     DPRINT("ImageView_PrintTo() not implemented\n");
1103 }
1104 
1105 VOID WINAPI
1106 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1107 {
1108     DPRINT("ImageView_PrintToA() not implemented\n");
1109 }
1110 
1111 VOID WINAPI
1112 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1113 {
1114     DPRINT("ImageView_PrintToW() not implemented\n");
1115 }
1116 
1117 BOOL WINAPI
1118 DllMain(IN HINSTANCE hinstDLL,
1119         IN DWORD dwReason,
1120         IN LPVOID lpvReserved)
1121 {
1122     switch (dwReason)
1123     {
1124         case DLL_PROCESS_ATTACH:
1125         case DLL_THREAD_ATTACH:
1126             hInstance = hinstDLL;
1127             break;
1128     }
1129 
1130     return TRUE;
1131 }
1132