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