Fix warnings in slideshow by using an int timerid
[gpg4win.git] / src / slideshow.cpp
1 /* slideshow.cpp - NSIS Helper DLL for a slideshow. -*- coding: latin-1; -*-
2  * Copyright (C) 2016 Intevation GmbH
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty. In no event will the authors be held liable for any
6  * damages arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any
9  * purpose, including commercial applications, and to alter it and
10  * redistribute it freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must
13  *    not claim that you wrote the original software. If you use this
14  *    software in a product, an acknowledgment in the product
15  *    documentation would be appreciated but is not required.
16  *
17  * 2. Altered source versions must be plainly marked as such, and must
18  *    not be misrepresented as being the original software.
19  *
20  * 3. This notice may not be removed or altered from any source
21  *    distribution.
22  ************************************************************
23  * The code is heavily based on the Slideshow
24  * plugin from http://wiz0u.free.fr/prog/nsisSlideshow
25  *
26  * It was slightly modified and adapted by:
27  * 2016 Andre Heinecke <aheinecke@intevation.de>
28  *
29  * Version 1.7 was Licensed at the time of copying (28.6.2016) as:
30  * Copyright (c) 2009-2011 Olivier Marcoux
31  *
32  * This software is provided 'as-is', without any express or implied warranty. In no
33  * event will the authors be held liable for any damages arising from the use of this
34  * software.
35  *
36  * Permission is granted to anyone to use this software for any purpose, including
37  * commercial applications, and to alter it and redistribute it freely, subject to the
38  * following restrictions:
39  *
40  * 1. The origin of this software must not be misrepresented; you must not claim
41  * that you wrote the original software. If you use this software in a product, an
42  * acknowledgment in the product documentation would be appreciated but is not
43  * required.
44  *
45  * 2. Altered source versions must be plainly marked as such, and must not be
46  * misrepresented as being the original software.
47  *
48  * 3. This notice may not be removed or altered from any source distribution.
49  ************************************************************
50  */
51
52 #include <windows.h>
53 #include <shlwapi.h>
54 #include <iimgctx.h>
55 #include <stdio.h>
56 #include "exdll.h"
57 #include <initguid.h>
58
59 static unsigned int timerid = 0xBEEF;
60
61 #ifndef _countof
62 #define _countof(A) (sizeof(A)/sizeof((A)[0]))
63 #endif
64
65 #ifndef INITGUID
66 #define INITGUID
67 #endif
68
69 __CRT_UUID_DECL(IImgCtx,
70                 0x3050f3d7, 0x98b5, 0x11cf,
71                 0xbb, 0x82, 0x00, 0xaa, 0x00, 0xbd, 0xce, 0x0b);
72
73 DEFINE_GUID(IID_IImgCtx,
74             0x3050f3d7, 0x98b5, 0x11cf,
75             0xbb, 0x82, 0x00, 0xaa, 0x00, 0xbd, 0xce, 0x0b);
76
77 DEFINE_GUID(CLSID_IImgCtx,
78             0x3050f3d6, 0x98b5, 0x11cf,
79             0xbb, 0x82, 0x00, 0xaa, 0x00, 0xbd, 0xce, 0x0b);
80
81 #undef INITGUID
82
83 /* array of precalculated alpha values for a linear crossfade in 15 steps */
84 const BYTE SCA_Steps[15] = { 17, 18, 20, 21, 23, 26, 28, 32, 36, 43, 51, 64, 85, 127, 255 };
85 int     g_step = 0;
86 HWND    g_hWnd = NULL;
87 HDC     g_hdcMem = NULL;
88 HBITMAP g_hbmMem = NULL;
89 TCHAR   g_autoPath[MAX_PATH];
90 LPTSTR  g_autoBuffer = NULL;
91 LPTSTR  g_autoNext = NULL;
92 int     g_autoDelay = 0;
93 RECT    rDest;
94 int     wDest, hDest;
95 WNDPROC lpPrevWndFunc = NULL;
96 enum HAlign {
97     HALIGN_CENTER = 0,
98     HALIGN_LEFT,
99     HALIGN_RIGHT
100 } iHAlign;
101 enum VAlign {
102     VALIGN_CENTER = 0,
103     VALIGN_TOP,
104     VALIGN_BOTTOM
105 } iVAlign;
106 enum Fit {
107     FIT_STRETCH = 0,
108     FIT_WIDTH,
109     FIT_HEIGHT,
110     FIT_BOTH
111 } iFit;
112 COLORREF captionColor;
113
114 static LRESULT CALLBACK slide_WndProc(HWND, UINT, WPARAM, LPARAM);
115
116 /*****************************************************
117  * Abort: stops current processing, detach from slide_WndProc and release resources
118  *****************************************************/
119 static void
120 slide_abort(bool stayAuto = false)
121 {
122   KillTimer(g_hWnd, timerid);
123   if (!stayAuto)
124     {
125       if (lpPrevWndFunc != NULL && IsWindow(g_hWnd))
126         {
127           if ((WNDPROC) GetWindowLong(g_hWnd, GWL_WNDPROC) == slide_WndProc)
128             SetWindowLongPtr(g_hWnd, GWL_WNDPROC, (long)lpPrevWndFunc);
129         }
130       lpPrevWndFunc = NULL;
131       GlobalFree(g_autoBuffer);
132       g_autoBuffer = NULL;
133       g_autoNext = NULL;
134       g_autoDelay = false;
135     }
136   DeleteDC(g_hdcMem);
137   g_hdcMem = NULL;
138   DeleteObject(g_hbmMem);
139   g_hbmMem = NULL;
140 }
141
142 static void
143 slide_NewImage(LPCTSTR imgPath, LPCTSTR caption, int duration)
144 {
145 #ifdef _UNICODE
146   LPCWSTR imgPathW = imgPath;
147 #else
148   WCHAR imgPathW[MAX_PATH];
149   MultiByteToWideChar(CP_ACP, 0, imgPath, -1, imgPathW, _countof(imgPathW));
150 #endif
151   IImgCtx *pImage = NULL;
152   SIZE imgSize = {0, 0};
153   if (SUCCEEDED(CoCreateInstance(CLSID_IImgCtx, NULL, CLSCTX_ALL, IID_IImgCtx, (void**)&pImage)))
154     {
155       if (SUCCEEDED(pImage->Load(imgPathW, 0)))
156         {
157           DWORD dwState;
158           while (SUCCEEDED(pImage->GetStateInfo(&dwState, NULL, true)) && (dwState & (IMGLOAD_COMPLETE|IMGLOAD_ERROR)) == 0)
159             Sleep(20);
160           pImage->GetStateInfo(&dwState, &imgSize, true);
161         }
162       if (imgSize.cx == 0 || imgSize.cy == 0) // error path or format (IMGLOAD_ERROR)
163         {
164           pImage->Release();
165           pImage = NULL;
166         }
167     }
168   if (pImage == NULL)
169     return;
170
171   // fit image
172   wDest = rDest.right - rDest.left;
173   hDest = rDest.bottom - rDest.top;
174   if (iFit == FIT_BOTH)
175     iFit = (wDest*imgSize.cy > imgSize.cx*hDest) ? FIT_HEIGHT : FIT_WIDTH;
176   if (iFit == FIT_HEIGHT)
177     wDest = (imgSize.cx * hDest) / imgSize.cy;
178   else if (iFit == FIT_WIDTH)
179     hDest = (imgSize.cy * wDest) / imgSize.cx;
180
181   // align image
182   if (iHAlign == HALIGN_CENTER) rDest.left = (rDest.left + rDest.right - wDest) / 2;
183   else if (iHAlign == HALIGN_RIGHT) rDest.left = rDest.right - wDest;
184   if (iVAlign == VALIGN_CENTER) rDest.top  = (rDest.top + rDest.bottom - hDest) / 2;
185   else if (iVAlign == VALIGN_BOTTOM) rDest.top = rDest.bottom - hDest;
186   rDest.right = rDest.left + wDest;
187   rDest.bottom = rDest.top + hDest;
188
189   // create memory DC & Bitmap compatible with window's DC
190   HDC hWndDC = GetDC(g_hWnd);
191   g_hdcMem = CreateCompatibleDC(hWndDC);
192   g_hbmMem = CreateCompatibleBitmap(hWndDC, wDest, hDest);
193   ReleaseDC(g_hWnd, hWndDC);
194   SelectObject(g_hdcMem, g_hbmMem);
195
196   // paint image in memory DC
197   RECT bounds = { 0, 0, wDest, hDest };
198   pImage->Draw(g_hdcMem, &bounds);
199   pImage->Release(); // we don't need the image anymore
200
201   if (caption[0] != '\0')
202     {
203       LOGFONT lf;
204       GetObject((HFONT) ::SendMessage(g_hWnd, WM_GETFONT, 0, 0), sizeof(lf), &lf);
205       lf.lfHeight += lf.lfHeight/2;
206       HFONT hFont = CreateFontIndirect(&lf);
207       HGDIOBJ hOldFont = SelectObject(g_hdcMem, hFont);
208       SetTextColor(g_hdcMem, captionColor);
209       SetBkMode(g_hdcMem, TRANSPARENT);
210       SetTextAlign(g_hdcMem, TA_BOTTOM|TA_CENTER|TA_NOUPDATECP);
211       TextOut(g_hdcMem, wDest/2, hDest-10, caption, lstrlen(caption));
212       DeleteObject(SelectObject(g_hdcMem, hOldFont));
213     }
214
215   // replace windows procedure, start time and initiate first step
216   if (lpPrevWndFunc == NULL)
217     lpPrevWndFunc = (WNDPROC) SetWindowLongPtr(g_hWnd, GWL_WNDPROC, (long) slide_WndProc);
218   if (duration == 0)
219     {
220       g_step = _countof(SCA_Steps);
221       InvalidateRect(g_hWnd, NULL, FALSE); // no duration => force a WM_PAINT for immediate draw of picture
222       if (g_autoNext && g_autoDelay)
223         SetTimer(g_hWnd, timerid, g_autoDelay, NULL);
224     }
225   else
226     {
227       g_step = 0;
228       slide_WndProc(g_hWnd, WM_TIMER, timerid, 0); // first iteration right now
229       SetTimer(g_hWnd, timerid, duration/_countof(SCA_Steps), NULL);
230     }
231 }
232
233 static bool
234 slide_NextAuto()
235 {
236   LPTSTR scan;
237   if (g_autoNext == NULL)
238     return false;
239   if (*g_autoNext == '.')
240     {
241       g_autoNext = g_autoBuffer;
242       return false;
243     }
244   bool result = false;
245   if (*g_autoNext == '=')
246     g_autoNext++;
247   for (scan = g_autoNext; *scan; scan++)
248     if (*scan == ',') break;
249   if (*scan)
250     {
251       TCHAR imgPath[MAX_PATH];
252       *scan = '\0';
253       PathCombine(imgPath, g_autoPath, g_autoNext);
254       *scan = ',';
255       g_autoNext = scan+1;
256
257       int duration = StrToInt(g_autoNext);
258       for (scan = g_autoNext; *scan; scan++)
259         if (*scan == ',') break;
260       if (*scan)
261         {
262           g_autoNext = scan+1;
263           g_autoDelay = StrToInt(g_autoNext);
264           for (scan = g_autoNext; *scan; scan++)
265             if (*scan == ',') break;
266           if (*scan && (scan[1] == '"'))
267             {
268               g_autoNext = scan+2;
269               for (scan = g_autoNext; *scan; scan++)
270                 if (*scan == '"') break;
271               if (*scan)
272                 {
273                   TCHAR caption[MAX_PATH];
274                   lstrcpyn(caption, g_autoNext, scan-g_autoNext+1);
275                   g_autoNext = scan+1;
276                   slide_NewImage(imgPath, caption, duration);
277                   result = true;
278                 }
279             }
280         }
281     }
282   g_autoNext += lstrlen(g_autoNext);
283   g_autoNext++;
284   if (*g_autoNext == '\0')
285     g_autoNext = g_autoBuffer;
286   return result;
287 }
288
289 /*****************************************************
290  * overriden WndProc for NSIS wizard pane
291  *****************************************************/
292 static LRESULT CALLBACK slide_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
293 {
294   switch (uMsg)
295     {
296     case WM_TIMER:
297       if (wParam == timerid)
298         {
299           if (g_step == _countof(SCA_Steps))
300             {
301               slide_abort(true);
302               if (!slide_NextAuto())
303                 {
304                   slide_abort();
305                 }
306               return 0;
307             }
308           HDC hDC = GetDC(hWnd);
309           const BLENDFUNCTION ftn = { AC_SRC_OVER, 0, SCA_Steps[g_step++], 0 };
310           AlphaBlend(hDC, rDest.left, rDest.top, wDest, hDest, g_hdcMem, 0, 0, wDest, hDest, ftn);
311           ReleaseDC(hWnd, hDC);
312           if (g_step == _countof(SCA_Steps))
313             {
314               if (g_autoNext && g_autoDelay)
315                 SetTimer(hWnd, timerid, g_autoDelay, NULL);
316               else
317                 KillTimer(hWnd, timerid);
318             }
319           return 0;
320         }
321     case WM_PAINT:
322       if (g_hdcMem)
323         {
324           PAINTSTRUCT ps;
325           HDC hDC = BeginPaint(hWnd, &ps);
326           CallWindowProc(lpPrevWndFunc, hWnd, uMsg, wParam, lParam);
327           BitBlt(hDC, rDest.left, rDest.top, wDest, hDest, g_hdcMem, 0, 0, SRCCOPY);
328           EndPaint(hWnd, &ps);
329           return 0;
330         }
331       break;
332
333     case WM_CLOSE:
334       slide_abort();
335       break;
336     case WM_COMMAND:
337       if(LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK || LOWORD(wParam) == IDABORT)
338         slide_abort();
339       break;
340
341     default:
342       break;
343     }
344   return CallWindowProc(lpPrevWndFunc, hWnd, uMsg, wParam, lParam);
345 }
346
347 /*****************************************************
348  * NSIS Plugin "stop" entrypoint
349  *****************************************************/
350 #ifdef __cplusplus
351 extern "C" {
352 #endif
353 void __declspec(dllexport)
354 slide_stop(HWND hwndParent, int string_size, TCHAR *variables, stack_t **stacktop)
355 {
356   slide_abort();
357 }
358
359 /*****************************************************
360  * NSIS Plugin "show" entrypoint
361  *****************************************************/
362 void __declspec(dllexport)
363 slide_show(HWND hwndParent, int string_size, TCHAR *variables, stack_t **stacktop)
364 {
365   EXDLL_INIT();
366   slide_abort();
367
368   // argument default values
369   iHAlign = HALIGN_CENTER;
370   iVAlign = VALIGN_CENTER;
371   iFit    = FIT_BOTH;
372   g_hWnd = NULL;
373   captionColor = RGB(255,255,255);
374   int duration = 1000; // transition duration in ms (default = 1s)
375   TCHAR caption[MAX_PATH];
376   caption[0] = '\0';
377
378   // parse arguments
379   TCHAR arg[MAX_PATH];
380   LPTSTR argValue;
381   while(!popstring(arg, sizeof arg) && *arg == '/' && (argValue = StrChr(arg, '=')) != NULL)
382     {
383       *argValue++ = '\0';     // replace '=' by '\0'
384       if(lstrcmpi(arg, TEXT("/hwnd")) == 0)
385         StrToIntEx(argValue, STIF_SUPPORT_HEX, (int*) &g_hWnd);
386       else if(lstrcmpi(arg, TEXT("/fit")) == 0)
387         {
388           if(lstrcmpi(argValue, TEXT("height")) == 0)           iFit = FIT_HEIGHT;
389           else if(lstrcmpi(argValue, TEXT("width")) == 0)       iFit = FIT_WIDTH;
390           else if(lstrcmpi(argValue, TEXT("stretch")) == 0)     iFit = FIT_STRETCH;
391         }
392       else if(lstrcmpi(arg, TEXT("/halign")) == 0)
393         {
394           if(lstrcmpi(argValue, TEXT("left")) == 0) iHAlign = HALIGN_LEFT;
395           else if(lstrcmpi(argValue, TEXT("right")) == 0) iHAlign = HALIGN_RIGHT;
396         }
397       else if(lstrcmpi(arg, TEXT("/valign")) == 0)
398         {
399           if(lstrcmpi(argValue, TEXT("top")) == 0) iVAlign = VALIGN_TOP;
400           else if(lstrcmpi(argValue, TEXT("bottom")) == 0) iVAlign = VALIGN_BOTTOM;
401         }
402       else if(lstrcmpi(arg, TEXT("/duration")) == 0)
403         StrToIntEx(argValue, STIF_SUPPORT_HEX, &duration);
404       else if(lstrcmpi(arg, TEXT("/caption")) == 0)
405         lstrcpy(caption, argValue);
406       else if(lstrcmpi(arg, TEXT("/ccolor")) == 0)
407         StrToIntEx(argValue, STIF_SUPPORT_HEX, (int*) &captionColor);
408       else if(lstrcmpi(arg, TEXT("/auto")) == 0)
409         {
410           lstrcpy(g_autoPath, argValue);
411           PathRemoveFileSpec(g_autoPath);
412           HGLOBAL hMem = GlobalAlloc(GMEM_FIXED, 32767*sizeof(TCHAR));
413           DWORD count = GetPrivateProfileSection(getuservariable(INST_LANG), LPTSTR(hMem), 32767, argValue);
414           if (count == 0)
415             {
416               count = GetPrivateProfileSection(TEXT("1033"), LPTSTR(hMem), 32767, argValue);
417               if (count == 0)
418                 count = GetPrivateProfileSection(TEXT("0"), LPTSTR(hMem), 32767, argValue);
419             }
420           if (count)
421             {
422               g_autoBuffer = LPTSTR(GlobalReAlloc(hMem, (count+1)*sizeof(TCHAR), 0));
423               g_autoNext = g_autoBuffer;
424             }
425           else
426             GlobalFree(hMem);
427         }
428     }
429
430   // if target window not defined we'll search for default (the details listview)
431   if (g_hWnd == NULL)
432     {
433       g_hWnd = FindWindowEx(hwndParent, NULL, TEXT("#32770"), NULL);
434       if (g_hWnd == NULL)
435         return;
436       hwndParent = FindWindowEx(hwndParent, g_hWnd, TEXT("#32770"), NULL);
437       if (hwndParent != NULL && !IsWindowVisible(hwndParent))
438         g_hWnd = hwndParent;
439       if (g_hWnd == NULL)
440         return;
441       HWND hWnd = GetDlgItem(g_hWnd, 1016);
442       GetWindowRect(hWnd, &rDest);
443       ScreenToClient(g_hWnd, (LPPOINT) &rDest.left);
444       ScreenToClient(g_hWnd, (LPPOINT) &rDest.right);
445     }
446   else
447     GetClientRect(g_hWnd, &rDest);
448
449   // load new image
450   if (arg[0] == '\0')
451     return; // stop here if no filename
452
453   if (g_autoNext != NULL)
454     slide_NextAuto();
455   else
456     slide_NewImage(arg, caption, duration);
457 }
458
459 #ifdef __cplusplus
460 }
461 #endif