xref: /reactos/base/setup/welcome/welcome.c (revision 4561998a)
1 /*
2  *  ReactOS applications
3  *  Copyright (C) 2001, 2002, 2003 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 /*
20  * COPYRIGHT:   See COPYING in the top level directory
21  * PROJECT:     ReactOS "Welcome"/AutoRun application
22  * FILE:        base/setup/welcome/welcome.c
23  * PROGRAMMERS: Eric Kohl
24  *              Casper S. Hornstrup (chorns@users.sourceforge.net)
25  *              Hermes Belusca-Maito
26  *
27  * NOTE:
28  *   This utility can be customized by using localized INI configuration files.
29  *   The default strings are stored in the utility's resources.
30  */
31 
32 #include <stdarg.h>
33 #include <tchar.h>
34 
35 #include <windef.h>
36 #include <winbase.h>
37 #include <wingdi.h>
38 #include <winnls.h>
39 #include <winuser.h>
40 #include <shellapi.h>
41 #include <strsafe.h>
42 
43 #include <reactos/buildno.h>
44 
45 #include "resource.h"
46 
47 #define LIGHT_BLUE RGB(214, 239, 247)
48 #define DARK_BLUE  RGB(107, 123, 140)
49 
50 #define TITLE_WIDTH  480
51 #define TITLE_HEIGHT  93
52 
53 /* GLOBALS ******************************************************************/
54 
55 TCHAR szWindowClass[] = TEXT("WelcomeWindowClass");
56 TCHAR szAppTitle[80];
57 
58 HINSTANCE hInstance;
59 
60 HWND hWndMain = NULL;
61 
62 HWND hWndCheckButton = NULL;
63 HWND hWndCloseButton = NULL;
64 
65 BOOL bDisplayCheckBox = FALSE;
66 BOOL bDisplayExitBtn  = TRUE;
67 
68 #define BUFFER_SIZE 1024
69 
70 #define TOPIC_TITLE_LENGTH  80
71 #define TOPIC_DESC_LENGTH   1024
72 
73 typedef struct _TOPIC
74 {
75     HBITMAP hBitmap;
76     HWND hWndButton;
77 
78     /*
79      * TRUE : szCommand contains a command (e.g. executable to run);
80      * FALSE: szCommand contains a custom "Welcome"/AutoRun action.
81      */
82     BOOL bIsCommand;
83 
84     TCHAR szText[80];
85     TCHAR szTitle[TOPIC_TITLE_LENGTH];
86     TCHAR szDesc[TOPIC_DESC_LENGTH];
87     TCHAR szCommand[512];
88     TCHAR szArgs[512];
89 } TOPIC, *PTOPIC;
90 
91 DWORD dwNumberTopics = 0;
92 PTOPIC* pTopics = NULL;
93 
94 WNDPROC fnOldBtn;
95 
96 TCHAR szDefaultTitle[TOPIC_TITLE_LENGTH];
97 TCHAR szDefaultDesc[TOPIC_DESC_LENGTH];
98 
99 #define TOPIC_BTN_ID_BASE   100
100 
101 INT nTopic = -1;        // Active (focused) topic
102 INT nDefaultTopic = -1; // Default selected topic
103 
104 HDC hdcMem = NULL;
105 HBITMAP hTitleBitmap = NULL;
106 HBITMAP hDefaultTopicBitmap = NULL;
107 
108 HFONT hFontTopicButton;
109 HFONT hFontTopicTitle;
110 HFONT hFontTopicDescription;
111 HFONT hFontCheckButton;
112 
113 HBRUSH hbrLightBlue;
114 HBRUSH hbrDarkBlue;
115 
116 RECT rcTitlePanel;
117 RECT rcLeftPanel;
118 RECT rcRightPanel;
119 
120 
121 INT_PTR CALLBACK
122 MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
123 
124 
125 /* FUNCTIONS ****************************************************************/
126 
127 INT GetLocaleName(IN LCID Locale, OUT LPTSTR lpLCData, IN SIZE_T cchData)
128 {
129     INT cchRet, cchRet2;
130 
131     /* Try to retrieve the locale language name (LOCALE_SNAME is supported on Vista+) */
132     cchRet = GetLocaleInfo(Locale, LOCALE_SNAME, lpLCData, cchData);
133     if (cchRet || (GetLastError() != ERROR_INVALID_FLAGS))
134         return cchRet;
135 
136     /*
137      * We failed because LOCALE_SNAME was unrecognized, so try to manually build
138      * a language name in the form xx-YY (WARNING: this method has its limitations).
139      */
140     cchRet = GetLocaleInfo(Locale, LOCALE_SISO639LANGNAME, lpLCData, cchData);
141     if (cchRet <= 1)
142         return cchRet;
143 
144     lpLCData += (cchRet - 1);
145     cchData -= (cchRet - 1);
146     if (cchData <= 1)
147         return cchRet;
148 
149     /* Try to get the second part; we add the '-' separator only if we succeed */
150     cchRet2 = GetLocaleInfo(Locale, LOCALE_SISO3166CTRYNAME, lpLCData + 1, cchData - 1);
151     if (cchRet2 <= 1)
152         return cchRet;
153     cchRet += cchRet2; // 'cchRet' already counts '-'.
154 
155     *lpLCData = _T('-');
156 
157     return cchRet;
158 }
159 
160 VOID TranslateEscapes(IN OUT LPTSTR lpString)
161 {
162     LPTSTR pEscape = NULL; // Next backslash escape sequence.
163 
164     while (lpString && *lpString)
165     {
166         /* Find the next backslash escape sequence */
167         pEscape = _tcschr(lpString, _T('\\'));
168         if (!pEscape)
169             break;
170 
171         /* Go past the escape backslash */
172         lpString = pEscape + 1;
173 
174         /* Find which sequence it is */
175         switch (*lpString)
176         {
177             case _T('\0'):
178                 // *pEscape = _T('\0'); // Enable if one wants to convert \<NULL> into <NULL>.
179                 // lpString = pEscape + 1; // Loop will stop at the next iteration.
180                 break;
181 
182             /* New-line and carriage return */
183             case _T('n'): case _T('r'):
184             // case _T('\\'): // others?
185             // So far we only need to deal with the newlines.
186             {
187                 if (*lpString == _T('n'))
188                     *pEscape = _T('\n');
189                 else if (*lpString == _T('r'))
190                     *pEscape = _T('\r');
191 
192                 memmove(lpString, lpString + 1, (_tcslen(lpString + 1) + 1) * sizeof(TCHAR));
193                 break;
194             }
195 
196             /* \xhhhh hexadecimal character specification */
197             case _T('x'):
198             {
199                 LPTSTR lpStringNew;
200                 *pEscape = (WCHAR)_tcstoul(lpString + 1, &lpStringNew, 16);
201                 memmove(lpString, lpStringNew, (_tcslen(lpStringNew) + 1) * sizeof(TCHAR));
202                 break;
203             }
204 
205             /* Unknown escape sequence, ignore it */
206             default:
207                 lpString++;
208                 break;
209         }
210     }
211 }
212 
213 /*
214  * Expands the path for the ReactOS Installer "reactos.exe".
215  * See also base/system/userinit/userinit.c!StartInstaller()
216  */
217 BOOL
218 ExpandInstallerPath(
219     IN LPCTSTR lpInstallerName,
220     OUT LPTSTR lpInstallerPath,
221     IN SIZE_T PathSize)
222 {
223     SYSTEM_INFO SystemInfo;
224     SIZE_T cchInstallerNameLen;
225     PTSTR ptr;
226     DWORD dwAttribs;
227 
228     cchInstallerNameLen = _tcslen(lpInstallerName);
229     if (PathSize < cchInstallerNameLen)
230     {
231         /* The buffer is not large enough to contain the installer file name */
232         *lpInstallerPath = 0;
233         return FALSE;
234     }
235 
236     /*
237      * First, try to find the installer using the default drive, under
238      * the directory whose name corresponds to the currently-running
239      * CPU architecture.
240      */
241     GetSystemInfo(&SystemInfo);
242 
243     *lpInstallerPath = 0;
244     GetModuleFileName(NULL, lpInstallerPath, PathSize - cchInstallerNameLen - 1);
245     ptr = _tcschr(lpInstallerPath, _T('\\'));
246     if (ptr)
247         *++ptr = 0;
248     else
249         *lpInstallerPath = 0;
250 
251     /* Append the corresponding CPU architecture */
252     switch (SystemInfo.wProcessorArchitecture)
253     {
254         case PROCESSOR_ARCHITECTURE_INTEL:
255             StringCchCat(lpInstallerPath, PathSize, TEXT("I386"));
256             break;
257 
258         case PROCESSOR_ARCHITECTURE_MIPS:
259             StringCchCat(lpInstallerPath, PathSize, TEXT("MIPS"));
260             break;
261 
262         case PROCESSOR_ARCHITECTURE_ALPHA:
263             StringCchCat(lpInstallerPath, PathSize, TEXT("ALPHA"));
264             break;
265 
266         case PROCESSOR_ARCHITECTURE_PPC:
267             StringCchCat(lpInstallerPath, PathSize, TEXT("PPC"));
268             break;
269 
270         case PROCESSOR_ARCHITECTURE_SHX:
271             StringCchCat(lpInstallerPath, PathSize, TEXT("SHX"));
272             break;
273 
274         case PROCESSOR_ARCHITECTURE_ARM:
275             StringCchCat(lpInstallerPath, PathSize, TEXT("ARM"));
276             break;
277 
278         case PROCESSOR_ARCHITECTURE_IA64:
279             StringCchCat(lpInstallerPath, PathSize, TEXT("IA64"));
280             break;
281 
282         case PROCESSOR_ARCHITECTURE_ALPHA64:
283             StringCchCat(lpInstallerPath, PathSize, TEXT("ALPHA64"));
284             break;
285 
286         case PROCESSOR_ARCHITECTURE_AMD64:
287             StringCchCat(lpInstallerPath, PathSize, TEXT("AMD64"));
288             break;
289 
290         // case PROCESSOR_ARCHITECTURE_MSIL: /* .NET CPU-independent code */
291         case PROCESSOR_ARCHITECTURE_UNKNOWN:
292         default:
293             SystemInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_UNKNOWN;
294             break;
295     }
296 
297     if (SystemInfo.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_UNKNOWN)
298         StringCchCat(lpInstallerPath, PathSize, TEXT("\\"));
299     StringCchCat(lpInstallerPath, PathSize, lpInstallerName);
300 
301     dwAttribs = GetFileAttributes(lpInstallerPath);
302     if ((dwAttribs != INVALID_FILE_ATTRIBUTES) &&
303         !(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
304     {
305         /* We have found the installer */
306         return TRUE;
307     }
308 
309     /*
310      * We failed. Try to find the installer from either the current
311      * ReactOS installation directory, or from our current directory.
312      */
313     *lpInstallerPath = 0;
314     if (GetWindowsDirectory(lpInstallerPath, PathSize - cchInstallerNameLen - 1))
315         StringCchCat(lpInstallerPath, PathSize, TEXT("\\"));
316     StringCchCat(lpInstallerPath, PathSize, lpInstallerName);
317 
318     dwAttribs = GetFileAttributes(lpInstallerPath);
319     if ((dwAttribs != INVALID_FILE_ATTRIBUTES) &&
320         !(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
321     {
322         /* We have found the installer */
323         return TRUE;
324     }
325 
326     /* Installer not found */
327     *lpInstallerPath = 0;
328     return FALSE;
329 }
330 
331 VOID InitializeTopicList(VOID)
332 {
333     dwNumberTopics = 0;
334     pTopics = NULL;
335 }
336 
337 PTOPIC AddNewTopic(VOID)
338 {
339     PTOPIC pTopic, *pTopicsTmp;
340 
341     /* Allocate (or reallocate) the list of topics */
342     if (!pTopics)
343         pTopicsTmp = HeapAlloc(GetProcessHeap(), 0, (dwNumberTopics + 1) * sizeof(*pTopics));
344     else
345         pTopicsTmp = HeapReAlloc(GetProcessHeap(), 0, pTopics, (dwNumberTopics + 1) * sizeof(*pTopics));
346     if (!pTopicsTmp)
347         return NULL; // Cannot reallocate more
348     pTopics = pTopicsTmp;
349 
350     /* Allocate a new topic entry */
351     pTopic = HeapAlloc(GetProcessHeap(), 0, sizeof(*pTopic));
352     if (!pTopic)
353         return NULL; // Cannot reallocate more
354     pTopics[dwNumberTopics++] = pTopic;
355 
356     /* Return the allocated topic entry */
357     return pTopic;
358 }
359 
360 PTOPIC
361 AddNewTopicEx(
362     IN LPTSTR szText  OPTIONAL,
363     IN LPTSTR szTitle OPTIONAL,
364     IN LPTSTR szDesc  OPTIONAL,
365     IN LPTSTR szCommand OPTIONAL,
366     IN LPTSTR szArgs    OPTIONAL,
367     IN LPTSTR szAction  OPTIONAL)
368 {
369     PTOPIC pTopic = AddNewTopic();
370     if (!pTopic)
371         return NULL;
372 
373     if (szText && *szText)
374         StringCchCopy(pTopic->szText, ARRAYSIZE(pTopic->szText), szText);
375     else
376         *pTopic->szText = 0;
377 
378     if (szTitle && *szTitle)
379         StringCchCopy(pTopic->szTitle, ARRAYSIZE(pTopic->szTitle), szTitle);
380     else
381         *pTopic->szTitle = 0;
382 
383     if (szDesc && *szDesc)
384     {
385         StringCchCopy(pTopic->szDesc, ARRAYSIZE(pTopic->szDesc), szDesc);
386         TranslateEscapes(pTopic->szDesc);
387     }
388     else
389     {
390         *pTopic->szDesc = 0;
391     }
392 
393     if (szCommand && *szCommand)
394     {
395         pTopic->bIsCommand = TRUE;
396 
397         /* Check for special applications: ReactOS Installer */
398         if (_tcsicmp(szCommand, TEXT("reactos.exe")) == 0)
399         {
400             ExpandInstallerPath(szCommand, pTopic->szCommand, ARRAYSIZE(pTopic->szCommand));
401         }
402         else
403         {
404             /* Expand any environment string in the command line */
405             DWORD dwSize = ExpandEnvironmentStringsW(szCommand, NULL, 0);
406             if (dwSize <= ARRAYSIZE(pTopic->szCommand))
407                 ExpandEnvironmentStringsW(szCommand, pTopic->szCommand, ARRAYSIZE(pTopic->szCommand));
408             else
409                 StringCchCopy(pTopic->szCommand, ARRAYSIZE(pTopic->szCommand), szCommand);
410         }
411     }
412     else
413     {
414         pTopic->bIsCommand = FALSE;
415         *pTopic->szCommand = 0;
416     }
417 
418     /* Only care about command arguments if we actually have a command */
419     if (*pTopic->szCommand)
420     {
421         if (szArgs && *szArgs)
422         {
423             StringCchCopy(pTopic->szArgs, ARRAYSIZE(pTopic->szArgs), szArgs);
424         }
425         else
426         {
427             /* Check for special applications: ReactOS Shell */
428             if (/* pTopic->szCommand && */ *pTopic->szCommand &&
429                 _tcsicmp(pTopic->szCommand, TEXT("explorer.exe")) == 0)
430             {
431 #if 0
432                 TCHAR CurrentDir[MAX_PATH];
433                 GetCurrentDirectory(ARRAYSIZE(CurrentDir), CurrentDir);
434 #endif
435                 StringCchCopy(pTopic->szArgs, ARRAYSIZE(pTopic->szArgs), TEXT("\\"));
436             }
437             else
438             {
439                 *pTopic->szArgs = 0;
440             }
441         }
442     }
443     else
444     {
445         *pTopic->szArgs = 0;
446     }
447 
448     /* Only care about custom actions if we actually don't have a command */
449     if (!*pTopic->szCommand && szAction && *szAction)
450     {
451         /*
452          * Re-use the pTopic->szCommand member. We distinguish with respect to
453          * a regular command by using the pTopic->bIsCommand flag.
454          */
455         pTopic->bIsCommand = FALSE;
456         StringCchCopy(pTopic->szCommand, ARRAYSIZE(pTopic->szCommand), szAction);
457         TranslateEscapes(pTopic->szCommand);
458     }
459 
460     return pTopic;
461 }
462 
463 static VOID
464 LoadLocalizedResourcesInternal(VOID)
465 {
466 #define MAX_NUMBER_INTERNAL_TOPICS  3
467 
468     UINT i;
469     LPTSTR lpszCommand, lpszAction;
470     TOPIC newTopic, *pTopic;
471 
472     for (i = 0; i < MAX_NUMBER_INTERNAL_TOPICS; ++i)
473     {
474         lpszCommand = NULL, lpszAction = NULL;
475 
476         /* Retrieve the information */
477         if (!LoadString(hInstance, IDS_TOPIC_BUTTON0 + i, newTopic.szText, ARRAYSIZE(newTopic.szText)))
478             *newTopic.szText = 0;
479         if (!LoadString(hInstance, IDS_TOPIC_TITLE0 + i, newTopic.szTitle, ARRAYSIZE(newTopic.szTitle)))
480             *newTopic.szTitle = 0;
481         if (!LoadString(hInstance, IDS_TOPIC_DESC0 + i, newTopic.szDesc, ARRAYSIZE(newTopic.szDesc)))
482             *newTopic.szDesc = 0;
483 
484         if (!LoadString(hInstance, IDS_TOPIC_COMMAND0 + i, newTopic.szCommand, ARRAYSIZE(newTopic.szCommand)))
485             *newTopic.szCommand = 0;
486 
487         /* Only care about command arguments if we actually have a command */
488         if (*newTopic.szCommand)
489         {
490             lpszCommand = newTopic.szCommand;
491             if (!LoadString(hInstance, IDS_TOPIC_CMD_ARGS0 + i, newTopic.szArgs, ARRAYSIZE(newTopic.szArgs)))
492                 *newTopic.szArgs = 0;
493         }
494         /* Only care about custom actions if we actually don't have a command */
495         else // if (!*newTopic.szCommand)
496         {
497             lpszAction = newTopic.szCommand;
498             if (!LoadString(hInstance, IDS_TOPIC_ACTION0 + i, newTopic.szCommand, ARRAYSIZE(newTopic.szCommand)))
499                 *newTopic.szCommand = 0;
500         }
501 
502         /* Allocate a new topic */
503         pTopic = AddNewTopicEx(newTopic.szText,
504                                newTopic.szTitle,
505                                newTopic.szDesc,
506                                lpszCommand,
507                                newTopic.szArgs,
508                                lpszAction);
509         if (!pTopic)
510             break; // Cannot reallocate more
511     }
512 }
513 
514 static BOOL
515 LoadLocalizedResourcesFromINI(LCID Locale, LPTSTR lpResPath)
516 {
517     DWORD dwRet;
518     DWORD dwAttribs;
519     DWORD dwSize;
520     TCHAR szBuffer[LOCALE_NAME_MAX_LENGTH];
521     TCHAR szIniPath[MAX_PATH];
522     LPTSTR lpszSections = NULL, lpszSection = NULL;
523     LPTSTR lpszCommand, lpszAction;
524     TOPIC newTopic, *pTopic;
525 
526     /* Retrieve the locale name (on which the INI file name is based) */
527     dwRet = (DWORD)GetLocaleName(Locale, szBuffer, ARRAYSIZE(szBuffer));
528     if (!dwRet)
529     {
530         /* Fall back to english (US) */
531         StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), TEXT("en-US"));
532     }
533 
534     /* Build the INI file name */
535     StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath),
536                     TEXT("%s\\%s.ini"), lpResPath, szBuffer);
537 
538     /* Verify that the file exists, otherwise fall back to english (US) */
539     dwAttribs = GetFileAttributes(szIniPath);
540     if ((dwAttribs == INVALID_FILE_ATTRIBUTES) ||
541         (dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
542     {
543         StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), TEXT("en-US"));
544 
545         StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath),
546                         TEXT("%s\\%s.ini"), lpResPath, szBuffer);
547     }
548 
549     /* Verify that the file exists, otherwise fall back to internal (localized) resource */
550     dwAttribs = GetFileAttributes(szIniPath);
551     if ((dwAttribs == INVALID_FILE_ATTRIBUTES) ||
552         (dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
553     {
554         return FALSE; // For localized resources, see the general function.
555     }
556 
557     /* Try to load the default localized strings */
558     GetPrivateProfileString(TEXT("Defaults"), TEXT("AppTitle"), TEXT("ReactOS - Welcome") /* default */,
559                             szAppTitle, ARRAYSIZE(szAppTitle), szIniPath);
560     GetPrivateProfileString(TEXT("Defaults"), TEXT("DefaultTopicTitle"), TEXT("") /* default */,
561                             szDefaultTitle, ARRAYSIZE(szDefaultTitle), szIniPath);
562     if (!GetPrivateProfileString(TEXT("Defaults"), TEXT("DefaultTopicDescription"), TEXT("") /* default */,
563                                  szDefaultDesc, ARRAYSIZE(szDefaultDesc), szIniPath))
564     {
565         *szDefaultDesc = 0;
566     }
567     else
568     {
569         TranslateEscapes(szDefaultDesc);
570     }
571 
572     /* Allocate a buffer big enough to hold all the section names */
573     for (dwSize = BUFFER_SIZE; ; dwSize += BUFFER_SIZE)
574     {
575         lpszSections = HeapAlloc(GetProcessHeap(), 0, dwSize * sizeof(TCHAR));
576         if (!lpszSections)
577             return TRUE; // FIXME!
578         dwRet = GetPrivateProfileSectionNames(lpszSections, dwSize, szIniPath);
579         if (dwRet < dwSize - 2)
580             break;
581         HeapFree(GetProcessHeap(), 0, lpszSections);
582     }
583 
584     /* Loop over the sections and load the topics */
585     lpszSection = lpszSections;
586     for (; lpszSection && *lpszSection; lpszSection += (_tcslen(lpszSection) + 1))
587     {
588         /* Ignore everything that is not a topic */
589         if (_tcsnicmp(lpszSection, TEXT("Topic"), 5) != 0)
590             continue;
591 
592         lpszCommand = NULL, lpszAction = NULL;
593 
594         /* Retrieve the information */
595         GetPrivateProfileString(lpszSection, TEXT("MenuText"), TEXT("") /* default */,
596                                 newTopic.szText, ARRAYSIZE(newTopic.szText), szIniPath);
597         GetPrivateProfileString(lpszSection, TEXT("Title"), TEXT("") /* default */,
598                                 newTopic.szTitle, ARRAYSIZE(newTopic.szTitle), szIniPath);
599         GetPrivateProfileString(lpszSection, TEXT("Description"), TEXT("") /* default */,
600                                 newTopic.szDesc, ARRAYSIZE(newTopic.szDesc), szIniPath);
601 
602         GetPrivateProfileString(lpszSection, TEXT("ConfigCommand"), TEXT("") /* default */,
603                                 newTopic.szCommand, ARRAYSIZE(newTopic.szCommand), szIniPath);
604 
605         /* Only care about command arguments if we actually have a command */
606         if (*newTopic.szCommand)
607         {
608             lpszCommand = newTopic.szCommand;
609             GetPrivateProfileString(lpszSection, TEXT("ConfigArgs"), TEXT("") /* default */,
610                                     newTopic.szArgs, ARRAYSIZE(newTopic.szArgs), szIniPath);
611         }
612         /* Only care about custom actions if we actually don't have a command */
613         else // if (!*newTopic.szCommand)
614         {
615             lpszAction = newTopic.szCommand;
616             GetPrivateProfileString(lpszSection, TEXT("Action"), TEXT("") /* default */,
617                                     newTopic.szCommand, ARRAYSIZE(newTopic.szCommand), szIniPath);
618         }
619 
620         /* Allocate a new topic */
621         pTopic = AddNewTopicEx(newTopic.szText,
622                                newTopic.szTitle,
623                                newTopic.szDesc,
624                                lpszCommand,
625                                newTopic.szArgs,
626                                lpszAction);
627         if (!pTopic)
628             break; // Cannot reallocate more
629     }
630 
631     HeapFree(GetProcessHeap(), 0, lpszSections);
632 
633     return TRUE;
634 }
635 
636 static VOID
637 LoadConfiguration(VOID)
638 {
639     DWORD dwAttribs;
640     BOOL  bLoadDefaultResources;
641     TCHAR szAppPath[MAX_PATH];
642     TCHAR szIniPath[MAX_PATH];
643     TCHAR szResPath[MAX_PATH];
644 
645     /* Initialize the topic list */
646     InitializeTopicList();
647 
648     /*
649      * First, try to load the default internal (localized) strings.
650      * They can be redefined by the localized INI files.
651      */
652     if (!LoadString(hInstance, IDS_APPTITLE, szAppTitle, ARRAYSIZE(szAppTitle)))
653         StringCchCopy(szAppTitle, ARRAYSIZE(szAppTitle), TEXT("ReactOS - Welcome"));
654     if (!LoadString(hInstance, IDS_DEFAULT_TOPIC_TITLE, szDefaultTitle, ARRAYSIZE(szDefaultTitle)))
655         *szDefaultTitle = 0;
656     if (!LoadString(hInstance, IDS_DEFAULT_TOPIC_DESC, szDefaultDesc, ARRAYSIZE(szDefaultDesc)))
657         *szDefaultDesc = 0;
658 
659     /* Retrieve the full path to this application */
660     GetModuleFileName(NULL, szAppPath, ARRAYSIZE(szAppPath));
661     if (*szAppPath)
662     {
663         LPTSTR lpFileName = _tcsrchr(szAppPath, _T('\\'));
664         if (lpFileName)
665             *lpFileName = 0;
666         else
667             *szAppPath = 0;
668     }
669 
670     /* Build the full INI file path name */
671     StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath), TEXT("%s\\welcome.ini"), szAppPath);
672 
673     /* Verify that the file exists, otherwise use the default configuration */
674     dwAttribs = GetFileAttributes(szIniPath);
675     if ((dwAttribs == INVALID_FILE_ATTRIBUTES) ||
676         (dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
677     {
678         /* Use the default internal (localized) resources */
679         LoadLocalizedResourcesInternal();
680         return;
681     }
682 
683     /* Load the settings from the INI configuration file */
684     bDisplayCheckBox = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("DisplayCheckBox"),  FALSE /* default */, szIniPath);
685     bDisplayExitBtn  = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("DisplayExitButton"), TRUE /* default */, szIniPath);
686 
687     /* Load the default internal (localized) resources if needed */
688     bLoadDefaultResources = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("LoadDefaultResources"), FALSE /* default */, szIniPath);
689     if (bLoadDefaultResources)
690         LoadLocalizedResourcesInternal();
691 
692     GetPrivateProfileString(TEXT("Welcome"), TEXT("ResourceDir"), TEXT("") /* default */,
693                             szResPath, ARRAYSIZE(szResPath), szIniPath);
694 
695     /* Set the current directory to the one of this application, and retrieve the resources */
696     SetCurrentDirectory(szAppPath);
697     if (!LoadLocalizedResourcesFromINI(LOCALE_USER_DEFAULT, szResPath))
698     {
699         /*
700          * Loading localized resources from INI file failed, try to load the
701          * internal resources only if they were not already loaded earlier.
702          */
703         if (!bLoadDefaultResources)
704             LoadLocalizedResourcesInternal();
705     }
706 }
707 
708 static VOID
709 FreeResources(VOID)
710 {
711     if (!pTopics)
712         return;
713 
714     while (dwNumberTopics--)
715     {
716         if (pTopics[dwNumberTopics])
717             HeapFree(GetProcessHeap(), 0, pTopics[dwNumberTopics]);
718     }
719     HeapFree(GetProcessHeap(), 0, pTopics);
720     pTopics = NULL;
721     dwNumberTopics = 0;
722 }
723 
724 #if 0
725 static VOID
726 ShowLastWin32Error(HWND hWnd)
727 {
728     LPTSTR lpMessageBuffer = NULL;
729     DWORD dwError = GetLastError();
730 
731     if (dwError == ERROR_SUCCESS)
732         return;
733 
734     if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
735                        FORMAT_MESSAGE_FROM_SYSTEM |
736                        FORMAT_MESSAGE_IGNORE_INSERTS,
737                        NULL,
738                        dwError,
739                        LANG_USER_DEFAULT,
740                        (LPTSTR)&lpMessageBuffer,
741                        0, NULL))
742     {
743         return;
744     }
745 
746     MessageBox(hWnd, lpMessageBuffer, szAppTitle, MB_OK | MB_ICONERROR);
747     LocalFree(lpMessageBuffer);
748 }
749 #endif
750 
751 int WINAPI
752 _tWinMain(HINSTANCE hInst,
753           HINSTANCE hPrevInstance,
754           LPTSTR lpszCmdLine,
755           int nCmdShow)
756 {
757     HANDLE hMutex = NULL;
758     WNDCLASSEX wndclass;
759     MSG msg;
760     HWND hWndFocus;
761     INT xPos, yPos;
762     INT xWidth, yHeight;
763     RECT rcWindow;
764     HICON hMainIcon;
765     HMENU hSystemMenu;
766     DWORD dwStyle = WS_OVERLAPPED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
767                     WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
768 
769     BITMAP BitmapInfo;
770     ULONG ulInnerWidth  = TITLE_WIDTH;
771     ULONG ulInnerHeight = (TITLE_WIDTH * 3) / 4;
772     ULONG ulTitleHeight = TITLE_HEIGHT + 3;
773 
774     UNREFERENCED_PARAMETER(hPrevInstance);
775     UNREFERENCED_PARAMETER(lpszCmdLine);
776 
777     /* Ensure only one instance is running */
778     hMutex = CreateMutex(NULL, FALSE, szWindowClass);
779     if (hMutex && (GetLastError() == ERROR_ALREADY_EXISTS))
780     {
781         /* If already started, find its window */
782         hWndMain = FindWindow(szWindowClass, NULL);
783 
784         /* Activate window */
785         ShowWindow(hWndMain, SW_SHOWNORMAL);
786         SetForegroundWindow(hWndMain);
787 
788         /* Close the mutex handle and quit */
789         CloseHandle(hMutex);
790         return 0;
791     }
792 
793 #if 0
794     /* Mirroring is enabled from within the resources */
795     switch (GetUserDefaultUILanguage())
796     {
797         case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
798             SetProcessDefaultLayout(LAYOUT_RTL);
799             break;
800 
801         default:
802             break;
803     }
804 #endif
805 
806     hInstance = hInst;
807 
808     /* Load icons */
809     hMainIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN));
810 
811     /* Register the window class */
812     wndclass.cbSize = sizeof(wndclass);
813     wndclass.style = CS_HREDRAW | CS_VREDRAW;
814     wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
815     wndclass.cbClsExtra = 0;
816     wndclass.cbWndExtra = 0;
817     wndclass.hInstance = hInstance;
818     wndclass.hIcon = hMainIcon;
819     wndclass.hIconSm = NULL;
820     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
821     wndclass.hbrBackground = NULL;
822     wndclass.lpszMenuName = NULL;
823     wndclass.lpszClassName = szWindowClass;
824 
825     RegisterClassEx(&wndclass);
826 
827     /* Load the banner bitmap, and compute the window dimensions */
828     hTitleBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TITLE_BITMAP));
829     if (hTitleBitmap)
830     {
831         GetObject(hTitleBitmap, sizeof(BitmapInfo), &BitmapInfo);
832         ulInnerWidth = BitmapInfo.bmWidth;
833         ulInnerHeight = (ulInnerWidth * 3) / 4;
834         ulTitleHeight = BitmapInfo.bmHeight + 3;
835         DeleteObject(hTitleBitmap);
836     }
837     ulInnerHeight -= GetSystemMetrics(SM_CYCAPTION);
838 
839     rcWindow.top = 0;
840     rcWindow.bottom = ulInnerHeight - 1;
841     rcWindow.left = 0;
842     rcWindow.right = ulInnerWidth - 1;
843 
844     AdjustWindowRect(&rcWindow, dwStyle, FALSE);
845     xWidth  = rcWindow.right - rcWindow.left;
846     yHeight = rcWindow.bottom - rcWindow.top;
847 
848     /* Compute the window position */
849     xPos = (GetSystemMetrics(SM_CXSCREEN) - xWidth) / 2;
850     yPos = (GetSystemMetrics(SM_CYSCREEN) - yHeight) / 2;
851 
852     rcTitlePanel.top = 0;
853     rcTitlePanel.bottom = ulTitleHeight;
854     rcTitlePanel.left = 0;
855     rcTitlePanel.right = ulInnerWidth - 1;
856 
857     rcLeftPanel.top = rcTitlePanel.bottom;
858     rcLeftPanel.bottom = ulInnerHeight - 1;
859     rcLeftPanel.left = 0;
860     rcLeftPanel.right = ulInnerWidth / 3;
861 
862     rcRightPanel.top = rcLeftPanel.top;
863     rcRightPanel.bottom = rcLeftPanel.bottom;
864     rcRightPanel.left = rcLeftPanel.right;
865     rcRightPanel.right = ulInnerWidth - 1;
866 
867     /* Load the configuration and the resources */
868     LoadConfiguration();
869 
870     /* Create main window */
871     hWndMain = CreateWindow(szWindowClass,
872                             szAppTitle,
873                             dwStyle,
874                             xPos,
875                             yPos,
876                             xWidth,
877                             yHeight,
878                             0,
879                             0,
880                             hInstance,
881                             NULL);
882 
883     hSystemMenu = GetSystemMenu(hWndMain, FALSE);
884     if (hSystemMenu)
885     {
886         RemoveMenu(hSystemMenu, SC_SIZE, MF_BYCOMMAND);
887         RemoveMenu(hSystemMenu, SC_MAXIMIZE, MF_BYCOMMAND);
888     }
889 
890     ShowWindow(hWndMain, nCmdShow);
891     UpdateWindow(hWndMain);
892 
893     while (GetMessage(&msg, NULL, 0, 0) != FALSE)
894     {
895         /* Check for ENTER key presses */
896         if (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN)
897         {
898             /*
899              * The user pressed the ENTER key. Retrieve the handle to the
900              * child window that has the keyboard focus, and send it a
901              * WM_COMMAND message.
902              */
903             hWndFocus = GetFocus();
904             if (hWndFocus)
905             {
906                 SendMessage(hWndMain, WM_COMMAND,
907                             (WPARAM)GetDlgCtrlID(hWndFocus), (LPARAM)hWndFocus);
908             }
909         }
910         /* Allow using keyboard navigation */
911         else if (!IsDialogMessage(hWndMain, &msg))
912         {
913             TranslateMessage(&msg);
914             DispatchMessage(&msg);
915         }
916     }
917 
918     /* Cleanup */
919     FreeResources();
920 
921     /* Close the mutex handle and quit */
922     CloseHandle(hMutex);
923     return msg.wParam;
924 }
925 
926 
927 INT_PTR CALLBACK
928 ButtonSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
929 {
930     static WPARAM wParamOld = 0;
931     static LPARAM lParamOld = 0;
932 
933     LONG i;
934 
935     if (uMsg == WM_MOUSEMOVE)
936     {
937         /* Ignore mouse-move messages on the same point */
938         if ((wParam == wParamOld) && (lParam == lParamOld))
939             return 0;
940 
941         /* Retrieve the topic index of this button */
942         i = GetWindowLongPtr(hWnd, GWLP_ID) - TOPIC_BTN_ID_BASE;
943 
944         /*
945          * Change the focus to this button if the current topic index differs
946          * (we will receive WM_SETFOCUS afterwards).
947          */
948         if (nTopic != i)
949             SetFocus(hWnd);
950 
951         wParamOld = wParam;
952         lParamOld = lParam;
953     }
954     else if (uMsg == WM_SETFOCUS)
955     {
956         /* Retrieve the topic index of this button */
957         i = GetWindowLongPtr(hWnd, GWLP_ID) - TOPIC_BTN_ID_BASE;
958 
959         /* Change the current topic index and repaint the description panel */
960         if (nTopic != i)
961         {
962             nTopic = i;
963             InvalidateRect(hWndMain, &rcRightPanel, TRUE);
964         }
965     }
966     else if (uMsg == WM_KILLFOCUS)
967     {
968         /*
969          * We lost focus, either because the user changed button focus,
970          * or because the main window to which we belong went inactivated.
971          * If we are in the latter case, we ignore the focus change.
972          * If we are in the former case, we reset to the default topic.
973          */
974         if (GetParent(hWnd) == GetForegroundWindow())
975         {
976             nTopic = -1;
977             InvalidateRect(hWndMain, &rcRightPanel, TRUE);
978         }
979     }
980 
981     return CallWindowProc(fnOldBtn, hWnd, uMsg, wParam, lParam);
982 }
983 
984 
985 static BOOL
986 RunAction(INT nTopic)
987 {
988     PCWSTR Command = NULL, Args = NULL;
989 
990     if (nTopic < 0)
991         return TRUE;
992 
993     Command = pTopics[nTopic]->szCommand;
994     if (/* !Command && */ !*Command)
995         return TRUE;
996 
997     /* Check for known actions */
998     if (!pTopics[nTopic]->bIsCommand)
999     {
1000         if (!_tcsicmp(Command, TEXT("<exit>")))
1001             return FALSE;
1002 
1003         if (!_tcsnicmp(Command, TEXT("<msg>"), 5))
1004         {
1005             MessageBox(hWndMain, Command + 5, TEXT("ReactOS"), MB_OK | MB_TASKMODAL);
1006             return TRUE;
1007         }
1008     }
1009     else
1010     /* Run the command */
1011     {
1012         Args = pTopics[nTopic]->szArgs;
1013         if (!*Args) Args = NULL;
1014         ShellExecute(NULL, NULL,
1015                      Command, Args,
1016                      NULL, SW_SHOWDEFAULT);
1017     }
1018 
1019     return TRUE;
1020 }
1021 
1022 
1023 static DWORD
1024 GetButtonHeight(HDC hDC,
1025                 HFONT hFont,
1026                 LPCTSTR szText,
1027                 DWORD dwWidth)
1028 {
1029     HFONT hOldFont;
1030     RECT rect;
1031 
1032     rect.left = 0;
1033     rect.right = dwWidth - 20;
1034     rect.top = 0;
1035     rect.bottom = 25;
1036 
1037     hOldFont = (HFONT)SelectObject(hDC, hFont);
1038     DrawText(hDC, szText, -1, &rect, DT_TOP | DT_CALCRECT | DT_WORDBREAK);
1039     SelectObject(hDC, hOldFont);
1040 
1041     return (rect.bottom-rect.top + 14);
1042 }
1043 
1044 
1045 static LRESULT
1046 OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
1047 {
1048     UINT i;
1049     INT nLength;
1050     HDC ScreenDC;
1051     LOGFONT lf;
1052     DWORD dwTop;
1053     DWORD dwHeight = 0;
1054     TCHAR szText[80];
1055 
1056     UNREFERENCED_PARAMETER(wParam);
1057     UNREFERENCED_PARAMETER(lParam);
1058 
1059     hbrLightBlue = CreateSolidBrush(LIGHT_BLUE);
1060     hbrDarkBlue  = CreateSolidBrush(DARK_BLUE);
1061 
1062     ZeroMemory(&lf, sizeof(lf));
1063 
1064     lf.lfEscapement  = 0;
1065     lf.lfOrientation = 0; // TA_BASELINE;
1066     // lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
1067     lf.lfCharSet = ANSI_CHARSET;
1068     lf.lfOutPrecision  = OUT_DEFAULT_PRECIS;
1069     lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1070     lf.lfQuality = DEFAULT_QUALITY;
1071     lf.lfPitchAndFamily = FF_DONTCARE;
1072     if (LoadString(hInstance, IDS_FONTNAME, lf.lfFaceName, ARRAYSIZE(lf.lfFaceName)) == 0)
1073         StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), TEXT("Tahoma"));
1074 
1075     /* Topic title font */
1076     lf.lfHeight = -18;
1077     lf.lfWidth  = 0;
1078     lf.lfWeight = FW_NORMAL;
1079     hFontTopicTitle = CreateFontIndirect(&lf);
1080 
1081     /* Topic description font */
1082     lf.lfHeight = -11;
1083     lf.lfWidth  = 0;
1084     lf.lfWeight = FW_THIN;
1085     hFontTopicDescription = CreateFontIndirect(&lf);
1086 
1087     /* Topic button font */
1088     lf.lfHeight = -11;
1089     lf.lfWidth  = 0;
1090     lf.lfWeight = FW_BOLD;
1091     hFontTopicButton = CreateFontIndirect(&lf);
1092 
1093     /* Load title bitmap */
1094     if (hTitleBitmap)
1095         hTitleBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TITLE_BITMAP));
1096 
1097     /* Load topic bitmaps */
1098     hDefaultTopicBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_DEFAULT_TOPIC_BITMAP));
1099     for (i = 0; i < dwNumberTopics; i++)
1100     {
1101         // FIXME: Not implemented yet!
1102         // pTopics[i]->hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TOPIC_BITMAP0 + i));
1103         pTopics[i]->hBitmap = NULL;
1104     }
1105 
1106     ScreenDC = GetWindowDC(hWnd);
1107     hdcMem = CreateCompatibleDC(ScreenDC);
1108     ReleaseDC(hWnd, ScreenDC);
1109 
1110     /* Load and create the menu buttons */
1111     dwTop = rcLeftPanel.top;
1112     for (i = 0; i < dwNumberTopics; i++)
1113     {
1114         if (*pTopics[i]->szText)
1115         {
1116             dwHeight = GetButtonHeight(hdcMem,
1117                                        hFontTopicButton,
1118                                        pTopics[i]->szText,
1119                                        rcLeftPanel.right - rcLeftPanel.left);
1120 
1121             pTopics[i]->hWndButton = CreateWindow(TEXT("BUTTON"),
1122                                                   pTopics[i]->szText,
1123                                                   (*pTopics[i]->szCommand ? 0 : WS_DISABLED) |
1124                                                       WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP |
1125                                                       BS_MULTILINE | BS_OWNERDRAW,
1126                                                   rcLeftPanel.left,
1127                                                   dwTop,
1128                                                   rcLeftPanel.right - rcLeftPanel.left,
1129                                                   dwHeight,
1130                                                   hWnd,
1131                                                   (HMENU)IntToPtr(TOPIC_BTN_ID_BASE + i), // Similar to SetWindowLongPtr(GWLP_ID)
1132                                                   hInstance,
1133                                                   NULL);
1134             nDefaultTopic = i;
1135             SendMessage(pTopics[i]->hWndButton, WM_SETFONT, (WPARAM)hFontTopicButton, MAKELPARAM(TRUE, 0));
1136             fnOldBtn = (WNDPROC)SetWindowLongPtr(pTopics[i]->hWndButton, GWLP_WNDPROC, (DWORD_PTR)ButtonSubclassWndProc);
1137         }
1138         else
1139         {
1140             pTopics[i]->hWndButton = NULL;
1141         }
1142 
1143         dwTop += dwHeight;
1144     }
1145 
1146     /* Create the checkbox */
1147     if (bDisplayCheckBox)
1148     {
1149         nLength = LoadString(hInstance, IDS_CHECKTEXT, szText, ARRAYSIZE(szText));
1150         if (nLength > 0)
1151         {
1152             lf.lfHeight = -10;
1153             lf.lfWidth  = 0;
1154             lf.lfWeight = FW_THIN;
1155             hFontCheckButton = CreateFontIndirect(&lf);
1156 
1157             hWndCheckButton = CreateWindow(TEXT("BUTTON"),
1158                                            szText,
1159                                            WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP |
1160                                                BS_AUTOCHECKBOX | BS_MULTILINE /**/| BS_FLAT/**/,
1161                                            rcLeftPanel.left + 8,
1162                                            rcLeftPanel.bottom - 8 - 13,
1163                                            rcLeftPanel.right - rcLeftPanel.left - 16,
1164                                            13,
1165                                            hWnd,
1166                                            (HMENU)IDC_CHECKBUTTON,
1167                                            hInstance,
1168                                            NULL);
1169             SendMessage(hWndCheckButton, WM_SETFONT, (WPARAM)hFontCheckButton, MAKELPARAM(TRUE, 0));
1170         }
1171         else
1172         {
1173             hFontCheckButton = NULL;
1174             hWndCheckButton = NULL;
1175         }
1176     }
1177 
1178     /* Create the "Exit" button */
1179     if (bDisplayExitBtn)
1180     {
1181         nLength = LoadString(hInstance, IDS_CLOSETEXT, szText, ARRAYSIZE(szText));
1182         if (nLength > 0)
1183         {
1184             hWndCloseButton = CreateWindow(TEXT("BUTTON"),
1185                                            szText,
1186                                            WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_FLAT,
1187                                            rcRightPanel.right - 8 - 57,
1188                                            rcRightPanel.bottom - 8 - 21,
1189                                            57,
1190                                            21,
1191                                            hWnd,
1192                                            (HMENU)IDC_CLOSEBUTTON,
1193                                            hInstance,
1194                                            NULL);
1195             nDefaultTopic = -1;
1196             SendMessage(hWndCloseButton, WM_SETFONT, (WPARAM)hFontTopicButton, MAKELPARAM(TRUE, 0));
1197         }
1198         else
1199         {
1200             hWndCloseButton = NULL;
1201         }
1202     }
1203 
1204     return 0;
1205 }
1206 
1207 
1208 static LRESULT
1209 OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
1210 {
1211     UNREFERENCED_PARAMETER(lParam);
1212 
1213     /* Retrieve the low-word from wParam */
1214     wParam = LOWORD(wParam);
1215 
1216     /* Execute action */
1217     if (wParam == IDC_CLOSEBUTTON)
1218     {
1219         DestroyWindow(hWnd);
1220     }
1221     else if (wParam - TOPIC_BTN_ID_BASE < dwNumberTopics)
1222     {
1223         if (RunAction(wParam - TOPIC_BTN_ID_BASE) == FALSE)
1224             DestroyWindow(hWnd); // Corresponds to a <exit> action.
1225     }
1226 
1227     return 0;
1228 }
1229 
1230 
1231 static VOID
1232 PaintBanner(HDC hdc, LPRECT rcPanel)
1233 {
1234     HBITMAP hOldBitmap;
1235     HBRUSH hOldBrush;
1236 
1237     /* Title bitmap */
1238     hOldBitmap = (HBITMAP)SelectObject(hdcMem, hTitleBitmap);
1239     BitBlt(hdc,
1240            rcPanel->left,
1241            rcPanel->top,
1242            rcPanel->right - rcPanel->left,
1243            rcPanel->bottom - 3,
1244            hdcMem, 0, 0, SRCCOPY);
1245     SelectObject(hdcMem, hOldBitmap);
1246 
1247     /* Dark blue line */
1248     hOldBrush = (HBRUSH)SelectObject(hdc, hbrDarkBlue);
1249     PatBlt(hdc,
1250            rcPanel->left,
1251            rcPanel->bottom - 3,
1252            rcPanel->right - rcPanel->left,
1253            3,
1254            PATCOPY);
1255     SelectObject(hdc, hOldBrush);
1256 }
1257 
1258 
1259 static LRESULT
1260 OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
1261 {
1262     HPEN hPen;
1263     HPEN hOldPen;
1264     HDC hdc;
1265     PAINTSTRUCT ps;
1266     HBITMAP hOldBitmap = NULL;
1267     HBRUSH hOldBrush;
1268     HFONT hOldFont;
1269     RECT rcTitle, rcDescription;
1270     BITMAP bmpInfo;
1271     TCHAR szVersion[50];
1272     LPTSTR lpTitle = NULL, lpDesc = NULL;
1273 
1274     UNREFERENCED_PARAMETER(wParam);
1275     UNREFERENCED_PARAMETER(lParam);
1276 
1277     hdc = BeginPaint(hWnd, &ps);
1278 
1279     /* Banner panel */
1280     PaintBanner(hdc, &rcTitlePanel);
1281 
1282     /* Left panel */
1283     hOldBrush = (HBRUSH)SelectObject(hdc, hbrLightBlue);
1284     PatBlt(hdc,
1285            rcLeftPanel.left,
1286            rcLeftPanel.top,
1287            rcLeftPanel.right - rcLeftPanel.left,
1288            rcLeftPanel.bottom - rcLeftPanel.top,
1289            PATCOPY);
1290     SelectObject(hdc, hOldBrush);
1291 
1292     /* Right panel */
1293     hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(WHITE_BRUSH));
1294     PatBlt(hdc,
1295            rcRightPanel.left,
1296            rcRightPanel.top,
1297            rcRightPanel.right - rcRightPanel.left,
1298            rcRightPanel.bottom - rcRightPanel.top,
1299            PATCOPY);
1300     SelectObject(hdc, hOldBrush);
1301 
1302     /* Draw dark vertical line */
1303     hPen = CreatePen(PS_SOLID, 0, DARK_BLUE);
1304     hOldPen = (HPEN)SelectObject(hdc, hPen);
1305     MoveToEx(hdc, rcRightPanel.left, rcRightPanel.top, NULL);
1306     LineTo(hdc, rcRightPanel.left, rcRightPanel.bottom);
1307     SelectObject(hdc, hOldPen);
1308     DeleteObject(hPen);
1309 
1310     /* Draw topic bitmap */
1311     if ((nTopic == -1) && (hDefaultTopicBitmap))
1312     {
1313         GetObject(hDefaultTopicBitmap, sizeof(bmpInfo), &bmpInfo);
1314         hOldBitmap = (HBITMAP)SelectObject(hdcMem, hDefaultTopicBitmap);
1315         BitBlt(hdc,
1316                rcRightPanel.right - bmpInfo.bmWidth,
1317                rcRightPanel.bottom - bmpInfo.bmHeight,
1318                bmpInfo.bmWidth,
1319                bmpInfo.bmHeight,
1320                hdcMem,
1321                0,
1322                0,
1323                SRCCOPY);
1324     }
1325     else if ((nTopic != -1) && (pTopics[nTopic]->hBitmap))
1326     {
1327         GetObject(pTopics[nTopic]->hBitmap, sizeof(bmpInfo), &bmpInfo);
1328         hOldBitmap = (HBITMAP)SelectObject(hdcMem, pTopics[nTopic]->hBitmap);
1329         BitBlt(hdc,
1330                rcRightPanel.right - bmpInfo.bmWidth,
1331                rcRightPanel.bottom - bmpInfo.bmHeight,
1332                bmpInfo.bmWidth,
1333                bmpInfo.bmHeight,
1334                hdcMem,
1335                0,
1336                0,
1337                SRCCOPY);
1338     }
1339 
1340     if (nTopic == -1)
1341     {
1342         lpTitle = szDefaultTitle;
1343         lpDesc  = szDefaultDesc;
1344     }
1345     else
1346     {
1347         lpTitle = pTopics[nTopic]->szTitle;
1348         lpDesc  = pTopics[nTopic]->szDesc;
1349     }
1350 
1351     SetBkMode(hdc, TRANSPARENT);
1352 
1353     /* Draw version information */
1354     StringCchCopy(szVersion, ARRAYSIZE(szVersion),
1355                   TEXT("ReactOS ") TEXT(KERNEL_VERSION_STR));
1356 
1357     /*
1358      * Compute the original rect (position & size) of the version info,
1359      * depending whether the checkbox is displayed (version info in the
1360      * right panel) or not (version info in the left panel).
1361      */
1362     if (bDisplayCheckBox)
1363         rcTitle = rcRightPanel;
1364     else
1365         rcTitle = rcLeftPanel;
1366 
1367     rcTitle.left   = rcTitle.left + 8;
1368     rcTitle.right  = rcTitle.right - 5;
1369     rcTitle.top    = rcTitle.bottom - 43;
1370     rcTitle.bottom = rcTitle.bottom - 8;
1371 
1372     hOldFont = (HFONT)SelectObject(hdc, hFontTopicDescription);
1373     DrawText(hdc, szVersion, -1, &rcTitle, DT_BOTTOM | DT_CALCRECT | DT_SINGLELINE);
1374     SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
1375     DrawText(hdc, szVersion, -1, &rcTitle, DT_BOTTOM | DT_SINGLELINE);
1376     SelectObject(hdc, hOldFont);
1377 
1378     /* Draw topic title */
1379     rcTitle.left = rcRightPanel.left + 12;
1380     rcTitle.right = rcRightPanel.right - 8;
1381     rcTitle.top = rcRightPanel.top + 8;
1382     rcTitle.bottom = rcTitle.top + 57;
1383     hOldFont = (HFONT)SelectObject(hdc, hFontTopicTitle);
1384     DrawText(hdc, lpTitle, -1, &rcTitle, DT_TOP | DT_CALCRECT);
1385     SetTextColor(hdc, DARK_BLUE);
1386     DrawText(hdc, lpTitle, -1, &rcTitle, DT_TOP);
1387     SelectObject(hdc, hOldFont);
1388 
1389     /* Draw topic description */
1390     rcDescription.left = rcRightPanel.left + 12;
1391     rcDescription.right = rcRightPanel.right - 8;
1392     rcDescription.top = rcTitle.bottom + 8;
1393     rcDescription.bottom = rcRightPanel.bottom - 20;
1394     hOldFont = (HFONT)SelectObject(hdc, hFontTopicDescription);
1395     SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
1396     DrawText(hdc, lpDesc, -1, &rcDescription, DT_TOP | DT_WORDBREAK);
1397     SelectObject(hdc, hOldFont);
1398 
1399     SetBkMode(hdc, OPAQUE);
1400 
1401     SelectObject(hdcMem, hOldBrush);
1402     SelectObject(hdcMem, hOldBitmap);
1403 
1404     EndPaint(hWnd, &ps);
1405 
1406     return 0;
1407 }
1408 
1409 
1410 static LRESULT
1411 OnDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
1412 {
1413     LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam;
1414     HPEN hPen, hOldPen;
1415     HBRUSH hOldBrush;
1416     INT iBkMode;
1417     TCHAR szText[80];
1418 
1419     UNREFERENCED_PARAMETER(hWnd);
1420     UNREFERENCED_PARAMETER(wParam);
1421 
1422 #if 0
1423     /* Neither the checkbox button nor the close button implement owner-drawing */
1424     if (lpDis->hwndItem == hWndCheckButton)
1425         return 0;
1426     if (lpDis->hwndItem == hWndCloseButton)
1427     {
1428         DrawFrameControl(lpDis->hDC,
1429                          &lpDis->rcItem,
1430                          DFC_BUTTON,
1431                          DFCS_BUTTONPUSH | DFCS_FLAT);
1432         return TRUE;
1433     }
1434 #endif
1435 
1436     if (lpDis->CtlID == (ULONG)(TOPIC_BTN_ID_BASE + nTopic))
1437         hOldBrush = (HBRUSH)SelectObject(lpDis->hDC, GetStockObject(WHITE_BRUSH));
1438     else
1439         hOldBrush = (HBRUSH)SelectObject(lpDis->hDC, hbrLightBlue);
1440 
1441     PatBlt(lpDis->hDC,
1442            lpDis->rcItem.left,
1443            lpDis->rcItem.top,
1444            lpDis->rcItem.right,
1445            lpDis->rcItem.bottom,
1446            PATCOPY);
1447     SelectObject(lpDis->hDC, hOldBrush);
1448 
1449     hPen = CreatePen(PS_SOLID, 0, DARK_BLUE);
1450     hOldPen = (HPEN)SelectObject(lpDis->hDC, hPen);
1451     MoveToEx(lpDis->hDC, lpDis->rcItem.left, lpDis->rcItem.bottom - 1, NULL);
1452     LineTo(lpDis->hDC, lpDis->rcItem.right, lpDis->rcItem.bottom - 1);
1453     SelectObject(lpDis->hDC, hOldPen);
1454     DeleteObject(hPen);
1455 
1456     InflateRect(&lpDis->rcItem, -10, -4);
1457     OffsetRect(&lpDis->rcItem, 0, 1);
1458     GetWindowText(lpDis->hwndItem, szText, ARRAYSIZE(szText));
1459     SetTextColor(lpDis->hDC, GetSysColor(IsWindowEnabled(lpDis->hwndItem) ?
1460                                          COLOR_WINDOWTEXT : COLOR_GRAYTEXT));
1461     iBkMode = SetBkMode(lpDis->hDC, TRANSPARENT);
1462     DrawText(lpDis->hDC, szText, -1, &lpDis->rcItem, DT_TOP | DT_LEFT | DT_WORDBREAK);
1463     SetBkMode(lpDis->hDC, iBkMode);
1464 
1465     return TRUE;
1466 }
1467 
1468 
1469 static LRESULT
1470 OnMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
1471 {
1472     static WPARAM wParamOld = 0;
1473     static LPARAM lParamOld = 0;
1474 
1475     /* Ignore mouse-move messages on the same point */
1476     if ((wParam == wParamOld) && (lParam == lParamOld))
1477         return 0;
1478 
1479     /*
1480      * If the user moves the mouse over the main window, outside of the
1481      * topic buttons, reset the current topic to the default one and
1482      * change the focus to some other default button (to keep keyboard
1483      * navigation possible).
1484      */
1485     if (nTopic != -1)
1486     {
1487         INT nOldTopic = nTopic;
1488         nTopic = -1;
1489         /* Also repaint the buttons, otherwise nothing repaints... */
1490         InvalidateRect(pTopics[nOldTopic]->hWndButton, NULL, TRUE);
1491 
1492         /* Set the focus to some other default button */
1493         if (hWndCheckButton)
1494             SetFocus(hWndCheckButton);
1495         else if (hWndCloseButton)
1496             SetFocus(hWndCloseButton);
1497         // SetFocus(hWnd);
1498 
1499         /* Repaint the description panel */
1500         InvalidateRect(hWndMain, &rcRightPanel, TRUE);
1501     }
1502 
1503     wParamOld = wParam;
1504     lParamOld = lParam;
1505 
1506     return 0;
1507 }
1508 
1509 
1510 static LRESULT
1511 OnCtlColorStatic(HWND hWnd, WPARAM wParam, LPARAM lParam)
1512 {
1513     UNREFERENCED_PARAMETER(hWnd);
1514 
1515     if ((HWND)lParam == hWndCheckButton)
1516     {
1517         SetBkMode((HDC)wParam, TRANSPARENT);
1518         return (LRESULT)hbrLightBlue;
1519     }
1520 
1521     return 0;
1522 }
1523 
1524 
1525 static LRESULT
1526 OnActivate(HWND hWnd, WPARAM wParam, LPARAM lParam)
1527 {
1528     UNREFERENCED_PARAMETER(hWnd);
1529     UNREFERENCED_PARAMETER(lParam);
1530 
1531     if (wParam != WA_INACTIVE)
1532     {
1533         /*
1534          * The main window is re-activated, set the focus back to
1535          * either the current topic or a default button.
1536          */
1537         if (nTopic != -1)
1538             SetFocus(pTopics[nTopic]->hWndButton);
1539         else if (hWndCheckButton)
1540             SetFocus(hWndCheckButton);
1541         else if (hWndCloseButton)
1542             SetFocus(hWndCloseButton);
1543 
1544         // InvalidateRect(hWndMain, &rcRightPanel, TRUE);
1545     }
1546 
1547     return 0;
1548 }
1549 
1550 
1551 static LRESULT
1552 OnDestroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
1553 {
1554     UINT i;
1555 
1556     UNREFERENCED_PARAMETER(hWnd);
1557     UNREFERENCED_PARAMETER(wParam);
1558     UNREFERENCED_PARAMETER(lParam);
1559 
1560     for (i = 0; i < dwNumberTopics; i++)
1561     {
1562         if (pTopics[i]->hWndButton)
1563             DestroyWindow(pTopics[i]->hWndButton);
1564     }
1565 
1566     if (hWndCloseButton)
1567         DestroyWindow(hWndCloseButton);
1568 
1569     if (hWndCheckButton)
1570         DestroyWindow(hWndCheckButton);
1571 
1572     DeleteDC(hdcMem);
1573 
1574     /* Delete bitmaps */
1575     DeleteObject(hDefaultTopicBitmap);
1576     DeleteObject(hTitleBitmap);
1577     for (i = 0; i < dwNumberTopics; i++)
1578     {
1579         if (pTopics[i]->hBitmap)
1580             DeleteObject(pTopics[i]->hBitmap);
1581     }
1582 
1583     DeleteObject(hFontTopicTitle);
1584     DeleteObject(hFontTopicDescription);
1585     DeleteObject(hFontTopicButton);
1586 
1587     if (hFontCheckButton)
1588         DeleteObject(hFontCheckButton);
1589 
1590     DeleteObject(hbrLightBlue);
1591     DeleteObject(hbrDarkBlue);
1592 
1593     return 0;
1594 }
1595 
1596 
1597 INT_PTR CALLBACK
1598 MainWndProc(HWND hWnd,
1599             UINT uMsg,
1600             WPARAM wParam,
1601             LPARAM lParam)
1602 {
1603     switch (uMsg)
1604     {
1605         case WM_CREATE:
1606             return OnCreate(hWnd, wParam, lParam);
1607 
1608         case WM_COMMAND:
1609             return OnCommand(hWnd, wParam, lParam);
1610 
1611         case WM_ACTIVATE:
1612             return OnActivate(hWnd, wParam, lParam);
1613 
1614         case WM_PAINT:
1615             return OnPaint(hWnd, wParam, lParam);
1616 
1617         case WM_DRAWITEM:
1618             return OnDrawItem(hWnd, wParam, lParam);
1619 
1620         case WM_CTLCOLORSTATIC:
1621             return OnCtlColorStatic(hWnd, wParam, lParam);
1622 
1623         case WM_MOUSEMOVE:
1624             return OnMouseMove(hWnd, wParam, lParam);
1625 
1626         case WM_DESTROY:
1627             OnDestroy(hWnd, wParam, lParam);
1628             PostQuitMessage(0);
1629             return 0;
1630     }
1631 
1632     return DefWindowProc(hWnd, uMsg, wParam, lParam);
1633 }
1634 
1635 /* EOF */
1636