[SHIMGVW] Play gif animation (#934)
[reactos.git] / dll / win32 / shimgvw / shimgvw.c
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;
591 HDC hdc;
592
593 hdc = BeginPaint(hwnd, &ps);
594 if (!hdc)
595 {
596 DPRINT1("BeginPaint() failed\n");
597 return;
598 }
599
600 GdipCreateFromHDC(hdc, &graphics);
601 if (!graphics)
602 {
603 DPRINT1("GdipCreateFromHDC() failed\n");
604 return;
605 }
606
607 GdipGetImageWidth(image, &ImageWidth);
608 GdipGetImageHeight(image, &ImageHeight);
609
610 if (GetClientRect(hwnd, &rect))
611 {
612 FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
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 DPRINT("x = %d, y = %d, ImageWidth = %u, ImageHeight = %u\n");
621 DPRINT("rect.right = %ld, rect.bottom = %ld\n", rect.right, rect.bottom);
622 DPRINT("ZoomPercents = %d, ZoomedWidth = %d, ZoomedHeight = %d\n",
623 ZoomPercents, ZoomedWidth, ZoomedWidth);
624
625 if (ZoomPercents % 100 == 0)
626 {
627 GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
628 GdipSetSmoothingMode(graphics, SmoothingModeNone);
629 }
630 else
631 {
632 GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
633 GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
634 }
635
636 Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
637 GdipDrawImageRectI(graphics, image, x, y, ZoomedWidth, ZoomedHeight);
638 }
639 GdipDeleteGraphics(graphics);
640 EndPaint(hwnd, &ps);
641 }
642
643 static BOOL
644 ImageView_LoadSettings()
645 {
646 HKEY hKey;
647 DWORD dwSize;
648
649 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
650 {
651 dwSize = sizeof(SHIMGVW_SETTINGS);
652 if (RegQueryValueEx(hKey, _T("Settings"), NULL, NULL, (LPBYTE)&shiSettings, &dwSize) == ERROR_SUCCESS)
653 {
654 RegCloseKey(hKey);
655 return TRUE;
656 }
657
658 RegCloseKey(hKey);
659 }
660
661 return FALSE;
662 }
663
664 static VOID
665 ImageView_SaveSettings(HWND hwnd)
666 {
667 WINDOWPLACEMENT wp;
668 HKEY hKey;
669
670 ShowWindow(hwnd, SW_HIDE);
671 wp.length = sizeof(WINDOWPLACEMENT);
672 GetWindowPlacement(hwnd, &wp);
673
674 shiSettings.Left = wp.rcNormalPosition.left;
675 shiSettings.Top = wp.rcNormalPosition.top;
676 shiSettings.Right = wp.rcNormalPosition.right;
677 shiSettings.Bottom = wp.rcNormalPosition.bottom;
678 shiSettings.Maximized = (IsZoomed(hwnd) || (wp.flags & WPF_RESTORETOMAXIMIZED));
679
680 if (RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, NULL,
681 REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
682 {
683 RegSetValueEx(hKey, _T("Settings"), 0, REG_BINARY, (LPBYTE)&shiSettings, sizeof(SHIMGVW_SETTINGS));
684 RegCloseKey(hKey);
685 }
686 }
687
688 static BOOL
689 ImageView_CreateToolBar(HWND hwnd)
690 {
691 INT numButtons = sizeof(Buttons) / sizeof(Buttons[0]);
692
693 hToolBar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
694 WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | CCS_BOTTOM | TBSTYLE_TOOLTIPS,
695 0, 0, 0, 0, hwnd,
696 0, hInstance, NULL);
697 if(hToolBar != NULL)
698 {
699 HIMAGELIST hImageList;
700
701 SendMessage(hToolBar, TB_SETEXTENDEDSTYLE,
702 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS);
703
704 SendMessage(hToolBar, TB_BUTTONSTRUCTSIZE,
705 sizeof(Buttons[0]), 0);
706
707 hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1);
708
709 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_PREVICON), IMAGE_BITMAP,
710 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
711
712 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_NEXTICON), IMAGE_BITMAP,
713 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
714
715 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ZOOMPICON), IMAGE_BITMAP,
716 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
717
718 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ZOOMMICON), IMAGE_BITMAP,
719 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
720
721 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_SAVEICON), IMAGE_BITMAP,
722 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
723
724 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_PRINTICON), IMAGE_BITMAP,
725 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
726
727 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ROT1ICON), IMAGE_BITMAP,
728 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
729
730 ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ROT2ICON), IMAGE_BITMAP,
731 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
732
733 if (hImageList == NULL) return FALSE;
734
735 ImageList_Destroy((HIMAGELIST)SendMessage(hToolBar, TB_SETIMAGELIST,
736 0, (LPARAM)hImageList));
737
738 SendMessage(hToolBar, TB_ADDBUTTONS,
739 numButtons, (LPARAM)Buttons);
740
741 return TRUE;
742 }
743
744 return FALSE;
745 }
746
747 static void ImageView_OnTimer(HWND hwnd)
748 {
749 DWORD dwDelay;
750
751 KillTimer(hwnd, ANIME_TIMER_ID);
752 InvalidateRect(hwnd, NULL, TRUE);
753
754 if (Anime_Step(&dwDelay))
755 {
756 SetTimer(hwnd, ANIME_TIMER_ID, dwDelay, NULL);
757 }
758 }
759
760 LRESULT CALLBACK
761 ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
762 {
763 switch (Message)
764 {
765 case WM_PAINT:
766 {
767 ImageView_DrawImage(hwnd);
768 return 0L;
769 }
770 case WM_TIMER:
771 {
772 if (wParam == ANIME_TIMER_ID)
773 {
774 ImageView_OnTimer(hwnd);
775 return 0;
776 }
777 break;
778 }
779 }
780 return CallWindowProc(PrevProc, hwnd, Message, wParam, lParam);
781 }
782
783 static VOID
784 ImageView_InitControls(HWND hwnd)
785 {
786 MoveWindow(hwnd, shiSettings.Left, shiSettings.Top,
787 shiSettings.Right - shiSettings.Left,
788 shiSettings.Bottom - shiSettings.Top, TRUE);
789
790 if (shiSettings.Maximized) ShowWindow(hwnd, SW_MAXIMIZE);
791
792 hDispWnd = CreateWindowEx(0, WC_STATIC, _T(""),
793 WS_CHILD | WS_VISIBLE,
794 0, 0, 0, 0, hwnd, NULL, hInstance, NULL);
795
796 SetClassLongPtr(hDispWnd, GCL_STYLE, CS_HREDRAW | CS_VREDRAW);
797 PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) ImageView_DispWndProc);
798
799 ImageView_CreateToolBar(hwnd);
800 }
801
802 static VOID
803 ImageView_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
804 {
805 if (zDelta != 0)
806 {
807 ZoomInOrOut(zDelta > 0);
808 }
809 }
810
811 LRESULT CALLBACK
812 ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
813 {
814 switch (Message)
815 {
816 case WM_CREATE:
817 {
818 ImageView_InitControls(hwnd);
819 return 0L;
820 }
821
822 case WM_KEYDOWN:
823 switch (LOWORD(wParam))
824 {
825 case VK_LEFT:
826 PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_PREV, BN_CLICKED), (LPARAM)NULL);
827 break;
828
829 case VK_RIGHT:
830 PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_NEXT, BN_CLICKED), (LPARAM)NULL);
831 break;
832 }
833 break;
834
835 case WM_COMMAND:
836 {
837 switch (wParam)
838 {
839 case IDC_PREV:
840 {
841 currentFile = currentFile->Prev;
842 pLoadImageFromNode(currentFile, hwnd);
843 }
844
845 break;
846 case IDC_NEXT:
847 {
848 currentFile = currentFile->Next;
849 pLoadImageFromNode(currentFile, hwnd);
850 }
851
852 break;
853 case IDC_ZOOMP:
854 {
855 ZoomInOrOut(TRUE);
856 }
857 break;
858 case IDC_ZOOMM:
859 {
860 ZoomInOrOut(FALSE);
861 }
862 break;
863 case IDC_SAVE:
864 pSaveImageAs(hwnd);
865
866 break;
867 case IDC_PRINT:
868
869 break;
870 case IDC_ROT1:
871 {
872 GdipImageRotateFlip(image, Rotate270FlipNone);
873 ImageView_UpdateWindow(hwnd);
874 }
875
876 break;
877 case IDC_ROT2:
878 {
879 GdipImageRotateFlip(image, Rotate90FlipNone);
880 ImageView_UpdateWindow(hwnd);
881 }
882
883 break;
884 }
885 }
886 break;
887
888 case WM_MOUSEWHEEL:
889 ImageView_OnMouseWheel(hwnd,
890 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
891 (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
892 break;
893
894 case WM_NOTIFY:
895 {
896 LPNMHDR pnmhdr = (LPNMHDR)lParam;
897
898 switch (pnmhdr->code)
899 {
900 case TTN_GETDISPINFO:
901 {
902 LPTOOLTIPTEXT lpttt;
903 UINT idButton;
904
905 lpttt = (LPTOOLTIPTEXT)lParam;
906 idButton = (UINT)lpttt->hdr.idFrom;
907 lpttt->hinst = hInstance;
908
909 switch (idButton)
910 {
911 case IDC_PREV:
912 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_PREV_PIC);
913 break;
914 case IDC_NEXT:
915 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_NEXT_PIC);
916 break;
917 case IDC_ZOOMP:
918 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ZOOM_IN);
919 break;
920 case IDC_ZOOMM:
921 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ZOOM_OUT);
922 break;
923 case IDC_SAVE:
924 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_SAVEAS);
925 break;
926 case IDC_PRINT:
927 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_PRINT);
928 break;
929 case IDC_ROT1:
930 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ROT_COUNCW);
931 break;
932 case IDC_ROT2:
933 lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ROT_CLOCKW);
934 break;
935 }
936 return TRUE;
937 }
938 }
939 break;
940 }
941 case WM_SIZING:
942 {
943 LPRECT pRect = (LPRECT)lParam;
944 if (pRect->right-pRect->left < 350)
945 pRect->right = pRect->left + 350;
946
947 if (pRect->bottom-pRect->top < 290)
948 pRect->bottom = pRect->top + 290;
949 return TRUE;
950 }
951 case WM_SIZE:
952 {
953 RECT rc;
954 SendMessage(hToolBar, TB_AUTOSIZE, 0, 0);
955 GetWindowRect(hToolBar, &rc);
956 MoveWindow(hDispWnd, 1, 1, LOWORD(lParam) - 1, HIWORD(lParam) - (rc.bottom - rc.top) - 1, TRUE);
957 /* is it maximized or restored? */
958 if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)
959 {
960 /* reset zoom */
961 ResetZoom();
962 }
963 return 0L;
964 }
965 case WM_DESTROY:
966 {
967 ImageView_SaveSettings(hwnd);
968 SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) PrevProc);
969 PostQuitMessage(0);
970 break;
971 }
972 }
973
974 return DefWindowProc(hwnd, Message, wParam, lParam);
975 }
976
977 LONG WINAPI
978 ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
979 {
980 struct GdiplusStartupInput gdiplusStartupInput;
981 ULONG_PTR gdiplusToken;
982 WNDCLASS WndClass = {0};
983 TCHAR szBuf[512];
984 WCHAR szInitialFile[MAX_PATH];
985 HWND hMainWnd;
986 MSG msg;
987
988 if (!ImageView_LoadSettings())
989 {
990 shiSettings.Maximized = FALSE;
991 shiSettings.Left = 0;
992 shiSettings.Top = 0;
993 shiSettings.Right = 520;
994 shiSettings.Bottom = 400;
995 }
996
997 // Initialize GDI+
998 gdiplusStartupInput.GdiplusVersion = 1;
999 gdiplusStartupInput.DebugEventCallback = NULL;
1000 gdiplusStartupInput.SuppressBackgroundThread = FALSE;
1001 gdiplusStartupInput.SuppressExternalCodecs = FALSE;
1002
1003 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1004 pLoadImage(szFileName);
1005
1006 // Create the window
1007 WndClass.lpszClassName = _T("shimgvw_window");
1008 WndClass.lpfnWndProc = ImageView_WndProc;
1009 WndClass.hInstance = hInstance;
1010 WndClass.style = CS_HREDRAW | CS_VREDRAW;
1011 WndClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPICON));
1012 WndClass.hCursor = LoadCursor(hInstance, IDC_ARROW);
1013 WndClass.hbrBackground = NULL; /* less flicker */
1014
1015 if (!RegisterClass(&WndClass)) return -1;
1016
1017 LoadString(hInstance, IDS_APPTITLE, szBuf, sizeof(szBuf) / sizeof(TCHAR));
1018 hMainWnd = CreateWindow(_T("shimgvw_window"), szBuf,
1019 WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CAPTION,
1020 CW_USEDEFAULT, CW_USEDEFAULT,
1021 0, 0, NULL, NULL, hInstance, NULL);
1022
1023 // make sure the path has no quotes on it
1024 wcscpy(szInitialFile, szFileName);
1025 PathUnquoteSpacesW(szInitialFile);
1026
1027 currentFile = pBuildFileList(szInitialFile);
1028 if (currentFile)
1029 {
1030 pLoadImageFromNode(currentFile, hMainWnd);
1031 }
1032
1033 // Show it
1034 ShowWindow(hMainWnd, SW_SHOW);
1035 UpdateWindow(hMainWnd);
1036
1037 // Message Loop
1038 while(GetMessage(&msg,NULL,0,0))
1039 {
1040 TranslateMessage(&msg);
1041 DispatchMessageW(&msg);
1042 }
1043
1044 pFreeFileList(currentFile);
1045
1046 if (image)
1047 GdipDisposeImage(image);
1048
1049 Anime_FreeInfo();
1050
1051 GdiplusShutdown(gdiplusToken);
1052 return -1;
1053 }
1054
1055 VOID WINAPI
1056 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1057 {
1058 ImageView_CreateWindow(hwnd, (LPWSTR)path);
1059 }
1060
1061 VOID WINAPI
1062 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1063 {
1064 ImageView_CreateWindow(hwnd, (LPWSTR)path);
1065 }
1066
1067 VOID WINAPI
1068 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1069 {
1070 WCHAR szFile[MAX_PATH];
1071
1072 if (MultiByteToWideChar(CP_ACP, 0, (char*)path, strlen((char*)path)+1, szFile, MAX_PATH))
1073 {
1074 ImageView_CreateWindow(hwnd, (LPWSTR)szFile);
1075 }
1076 }
1077
1078 VOID WINAPI
1079 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1080 {
1081 DPRINT("ImageView_PrintTo() not implemented\n");
1082 }
1083
1084 VOID WINAPI
1085 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1086 {
1087 DPRINT("ImageView_PrintToA() not implemented\n");
1088 }
1089
1090 VOID WINAPI
1091 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1092 {
1093 DPRINT("ImageView_PrintToW() not implemented\n");
1094 }
1095
1096 BOOL WINAPI
1097 DllMain(IN HINSTANCE hinstDLL,
1098 IN DWORD dwReason,
1099 IN LPVOID lpvReserved)
1100 {
1101 switch (dwReason)
1102 {
1103 case DLL_PROCESS_ATTACH:
1104 case DLL_THREAD_ATTACH:
1105 hInstance = hinstDLL;
1106 break;
1107 }
1108
1109 return TRUE;
1110 }