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