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