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