xref: /reactos/base/setup/welcome/welcome.c (revision fe50c655)
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 VOID InitializeTopicList(VOID)
214 {
215     dwNumberTopics = 0;
216     pTopics = NULL;
217 }
218 
219 PTOPIC AddNewTopic(VOID)
220 {
221     PTOPIC pTopic, *pTopicsTmp;
222 
223     /* Allocate (or reallocate) the list of topics */
224     if (!pTopics)
225         pTopicsTmp = HeapAlloc(GetProcessHeap(), 0, (dwNumberTopics + 1) * sizeof(*pTopics));
226     else
227         pTopicsTmp = HeapReAlloc(GetProcessHeap(), 0, pTopics, (dwNumberTopics + 1) * sizeof(*pTopics));
228     if (!pTopicsTmp)
229         return NULL; // Cannot reallocate more
230     pTopics = pTopicsTmp;
231 
232     /* Allocate a new topic entry */
233     pTopic = HeapAlloc(GetProcessHeap(), 0, sizeof(*pTopic));
234     if (!pTopic)
235         return NULL; // Cannot reallocate more
236     pTopics[dwNumberTopics++] = pTopic;
237 
238     /* Return the allocated topic entry */
239     return pTopic;
240 }
241 
242 PTOPIC AddNewTopicEx(
243     IN LPTSTR szText  OPTIONAL,
244     IN LPTSTR szTitle OPTIONAL,
245     IN LPTSTR szDesc  OPTIONAL,
246     IN LPTSTR szCommand OPTIONAL,
247     IN LPTSTR szArgs    OPTIONAL,
248     IN LPTSTR szAction  OPTIONAL)
249 {
250     PTOPIC pTopic = AddNewTopic();
251     if (!pTopic)
252         return NULL;
253 
254     if (szText && *szText)
255         StringCchCopy(pTopic->szText, ARRAYSIZE(pTopic->szText), szText);
256     else
257         *pTopic->szText = 0;
258 
259     if (szTitle && *szTitle)
260         StringCchCopy(pTopic->szTitle, ARRAYSIZE(pTopic->szTitle), szTitle);
261     else
262         *pTopic->szTitle = 0;
263 
264     if (szDesc && *szDesc)
265     {
266         StringCchCopy(pTopic->szDesc, ARRAYSIZE(pTopic->szDesc), szDesc);
267         TranslateEscapes(pTopic->szDesc);
268     }
269     else
270     {
271         *pTopic->szDesc = 0;
272     }
273 
274     if (szCommand && *szCommand)
275     {
276         pTopic->bIsCommand = TRUE;
277         StringCchCopy(pTopic->szCommand, ARRAYSIZE(pTopic->szCommand), szCommand);
278     }
279     else
280     {
281         pTopic->bIsCommand = FALSE;
282         *pTopic->szCommand = 0;
283     }
284 
285     /* Only care about command arguments if we actually have a command */
286     if (*pTopic->szCommand)
287     {
288         if (szArgs && *szArgs)
289         {
290             StringCchCopy(pTopic->szArgs, ARRAYSIZE(pTopic->szArgs), szArgs);
291         }
292         else
293         {
294             /* Check for special applications: ReactOS Shell */
295             if (/* pTopic->szCommand && */ *pTopic->szCommand &&
296                 _tcsicmp(pTopic->szCommand, TEXT("explorer.exe")) == 0)
297             {
298 #if 0
299                 TCHAR CurrentDir[MAX_PATH];
300                 GetCurrentDirectory(ARRAYSIZE(CurrentDir), CurrentDir);
301 #endif
302                 StringCchCopy(pTopic->szArgs, ARRAYSIZE(pTopic->szArgs), TEXT("\\"));
303             }
304             else
305             {
306                 *pTopic->szArgs = 0;
307             }
308         }
309     }
310     else
311     {
312         *pTopic->szArgs = 0;
313     }
314 
315     /* Only care about custom actions if we actually don't have a command */
316     if (!*pTopic->szCommand && szAction && *szAction)
317     {
318         /*
319          * Re-use the pTopic->szCommand member. We distinguish with respect to
320          * a regular command by using the pTopic->bIsCommand flag.
321          */
322         pTopic->bIsCommand = FALSE;
323         StringCchCopy(pTopic->szCommand, ARRAYSIZE(pTopic->szCommand), szAction);
324         TranslateEscapes(pTopic->szCommand);
325     }
326 
327     return pTopic;
328 }
329 
330 static VOID
331 LoadLocalizedResourcesInternal(VOID)
332 {
333 #define MAX_NUMBER_INTERNAL_TOPICS  3
334 
335     UINT i;
336     LPTSTR lpszCommand, lpszAction;
337     TOPIC newTopic, *pTopic;
338 
339     for (i = 0; i < MAX_NUMBER_INTERNAL_TOPICS; ++i)
340     {
341         lpszCommand = NULL, lpszAction = NULL;
342 
343         /* Retrieve the information */
344         if (!LoadString(hInstance, IDS_TOPIC_BUTTON0 + i, newTopic.szText, ARRAYSIZE(newTopic.szText)))
345             *newTopic.szText = 0;
346         if (!LoadString(hInstance, IDS_TOPIC_TITLE0 + i, newTopic.szTitle, ARRAYSIZE(newTopic.szTitle)))
347             *newTopic.szTitle = 0;
348         if (!LoadString(hInstance, IDS_TOPIC_DESC0 + i, newTopic.szDesc, ARRAYSIZE(newTopic.szDesc)))
349             *newTopic.szDesc = 0;
350 
351         if (!LoadString(hInstance, IDS_TOPIC_COMMAND0 + i, newTopic.szCommand, ARRAYSIZE(newTopic.szCommand)))
352             *newTopic.szCommand = 0;
353 
354         /* Only care about command arguments if we actually have a command */
355         if (*newTopic.szCommand)
356         {
357             lpszCommand = newTopic.szCommand;
358             if (!LoadString(hInstance, IDS_TOPIC_CMD_ARGS0 + i, newTopic.szArgs, ARRAYSIZE(newTopic.szArgs)))
359                 *newTopic.szArgs = 0;
360         }
361         /* Only care about custom actions if we actually don't have a command */
362         else // if (!*newTopic.szCommand)
363         {
364             lpszAction = newTopic.szCommand;
365             if (!LoadString(hInstance, IDS_TOPIC_ACTION0 + i, newTopic.szCommand, ARRAYSIZE(newTopic.szCommand)))
366                 *newTopic.szCommand = 0;
367         }
368 
369         /* Allocate a new topic */
370         pTopic = AddNewTopicEx(newTopic.szText,
371                                newTopic.szTitle,
372                                newTopic.szDesc,
373                                lpszCommand,
374                                newTopic.szArgs,
375                                lpszAction);
376         if (!pTopic)
377             break; // Cannot reallocate more
378     }
379 }
380 
381 static BOOL
382 LoadLocalizedResourcesFromINI(LCID Locale, LPTSTR lpResPath)
383 {
384     DWORD dwRet;
385     DWORD dwSize;
386     TCHAR szBuffer[LOCALE_NAME_MAX_LENGTH];
387     TCHAR szIniPath[MAX_PATH];
388     LPTSTR lpszSections = NULL, lpszSection = NULL;
389     LPTSTR lpszCommand, lpszAction;
390     TOPIC newTopic, *pTopic;
391 
392     /* Retrieve the locale name (on which the INI file name is based) */
393     dwRet = (DWORD)GetLocaleName(Locale, szBuffer, ARRAYSIZE(szBuffer));
394     if (!dwRet)
395     {
396         /* Fall back to english (US) */
397         StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), TEXT("en-US"));
398     }
399 
400     /* Build the INI file name */
401     StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath),
402                     TEXT("%s\\%s.ini"), lpResPath, szBuffer);
403 
404     /* Verify that the file exists, otherwise fall back to english (US) */
405     if (GetFileAttributes(szIniPath) == INVALID_FILE_ATTRIBUTES)
406     {
407         StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), TEXT("en-US"));
408 
409         StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath),
410                         TEXT("%s\\%s.ini"), lpResPath, szBuffer);
411     }
412 
413     /* Verify that the file exists, otherwise fall back to internal (localized) resource */
414     if (GetFileAttributes(szIniPath) == INVALID_FILE_ATTRIBUTES)
415         return FALSE; // For localized resources, see the general function.
416 
417     /* Try to load the default localized strings */
418     GetPrivateProfileString(TEXT("Defaults"), TEXT("AppTitle"), TEXT("ReactOS - Welcome") /* default */,
419                             szAppTitle, ARRAYSIZE(szAppTitle), szIniPath);
420     GetPrivateProfileString(TEXT("Defaults"), TEXT("DefaultTopicTitle"), TEXT("") /* default */,
421                             szDefaultTitle, ARRAYSIZE(szDefaultTitle), szIniPath);
422     if (!GetPrivateProfileString(TEXT("Defaults"), TEXT("DefaultTopicDescription"), TEXT("") /* default */,
423                                  szDefaultDesc, ARRAYSIZE(szDefaultDesc), szIniPath))
424     {
425         *szDefaultDesc = 0;
426     }
427     else
428     {
429         TranslateEscapes(szDefaultDesc);
430     }
431 
432     /* Allocate a buffer big enough to hold all the section names */
433     for (dwSize = BUFFER_SIZE; ; dwSize += BUFFER_SIZE)
434     {
435         lpszSections = HeapAlloc(GetProcessHeap(), 0, dwSize * sizeof(TCHAR));
436         if (!lpszSections)
437             return TRUE; // FIXME!
438         dwRet = GetPrivateProfileSectionNames(lpszSections, dwSize, szIniPath);
439         if (dwRet < dwSize - 2)
440             break;
441         HeapFree(GetProcessHeap(), 0, lpszSections);
442     }
443 
444     /* Loop over the sections and load the topics */
445     lpszSection = lpszSections;
446     for (; lpszSection && *lpszSection; lpszSection += (_tcslen(lpszSection) + 1))
447     {
448         /* Ignore everything that is not a topic */
449         if (_tcsnicmp(lpszSection, TEXT("Topic"), 5) != 0)
450             continue;
451 
452         lpszCommand = NULL, lpszAction = NULL;
453 
454         /* Retrieve the information */
455         GetPrivateProfileString(lpszSection, TEXT("MenuText"), TEXT("") /* default */,
456                                 newTopic.szText, ARRAYSIZE(newTopic.szText), szIniPath);
457         GetPrivateProfileString(lpszSection, TEXT("Title"), TEXT("") /* default */,
458                                 newTopic.szTitle, ARRAYSIZE(newTopic.szTitle), szIniPath);
459         GetPrivateProfileString(lpszSection, TEXT("Description"), TEXT("") /* default */,
460                                 newTopic.szDesc, ARRAYSIZE(newTopic.szDesc), szIniPath);
461 
462         GetPrivateProfileString(lpszSection, TEXT("ConfigCommand"), TEXT("") /* default */,
463                                 newTopic.szCommand, ARRAYSIZE(newTopic.szCommand), szIniPath);
464 
465         /* Only care about command arguments if we actually have a command */
466         if (*newTopic.szCommand)
467         {
468             lpszCommand = newTopic.szCommand;
469             GetPrivateProfileString(lpszSection, TEXT("ConfigArgs"), TEXT("") /* default */,
470                                     newTopic.szArgs, ARRAYSIZE(newTopic.szArgs), szIniPath);
471         }
472         /* Only care about custom actions if we actually don't have a command */
473         else // if (!*newTopic.szCommand)
474         {
475             lpszAction = newTopic.szCommand;
476             GetPrivateProfileString(lpszSection, TEXT("Action"), TEXT("") /* default */,
477                                     newTopic.szCommand, ARRAYSIZE(newTopic.szCommand), szIniPath);
478         }
479 
480         /* Allocate a new topic */
481         pTopic = AddNewTopicEx(newTopic.szText,
482                                newTopic.szTitle,
483                                newTopic.szDesc,
484                                lpszCommand,
485                                newTopic.szArgs,
486                                lpszAction);
487         if (!pTopic)
488             break; // Cannot reallocate more
489     }
490 
491     HeapFree(GetProcessHeap(), 0, lpszSections);
492 
493     return TRUE;
494 }
495 
496 static VOID
497 LoadConfiguration(VOID)
498 {
499     BOOL  bLoadDefaultResources;
500     TCHAR szAppPath[MAX_PATH];
501     TCHAR szIniPath[MAX_PATH];
502     TCHAR szResPath[MAX_PATH];
503 
504     /* Initialize the topic list */
505     InitializeTopicList();
506 
507     /*
508      * First, try to load the default internal (localized) strings.
509      * They can be redefined by the localized INI files.
510      */
511     if (!LoadString(hInstance, IDS_APPTITLE, szAppTitle, ARRAYSIZE(szAppTitle)))
512         StringCchCopy(szAppTitle, ARRAYSIZE(szAppTitle), TEXT("ReactOS - Welcome"));
513     if (!LoadString(hInstance, IDS_DEFAULT_TOPIC_TITLE, szDefaultTitle, ARRAYSIZE(szDefaultTitle)))
514         *szDefaultTitle = 0;
515     if (!LoadString(hInstance, IDS_DEFAULT_TOPIC_DESC, szDefaultDesc, ARRAYSIZE(szDefaultDesc)))
516         *szDefaultDesc = 0;
517 
518     /* Retrieve the full path to this application */
519     GetModuleFileName(NULL, szAppPath, ARRAYSIZE(szAppPath));
520     if (*szAppPath)
521     {
522         LPTSTR lpFileName = _tcsrchr(szAppPath, _T('\\'));
523         if (lpFileName)
524             *lpFileName = 0;
525         else
526             *szAppPath = 0;
527     }
528 
529     /* Build the full INI file path name */
530     StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath), TEXT("%s\\welcome.ini"), szAppPath);
531 
532     /* Verify that the file exists, otherwise use the default configuration */
533     if (GetFileAttributes(szIniPath) == INVALID_FILE_ATTRIBUTES)
534     {
535         /* Use the default internal (localized) resources */
536         LoadLocalizedResourcesInternal();
537         return;
538     }
539 
540     /* Load the settings from the INI configuration file */
541     bDisplayCheckBox = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("DisplayCheckBox"),  FALSE /* default */, szIniPath);
542     bDisplayExitBtn  = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("DisplayExitButton"), TRUE /* default */, szIniPath);
543 
544     /* Load the default internal (localized) resources if needed */
545     bLoadDefaultResources = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("LoadDefaultResources"), FALSE /* default */, szIniPath);
546     if (bLoadDefaultResources)
547         LoadLocalizedResourcesInternal();
548 
549     GetPrivateProfileString(TEXT("Welcome"), TEXT("ResourceDir"), TEXT("") /* default */,
550                             szResPath, ARRAYSIZE(szResPath), szIniPath);
551 
552     /* Set the current directory to the one of this application, and retrieve the resources */
553     SetCurrentDirectory(szAppPath);
554     if (!LoadLocalizedResourcesFromINI(LOCALE_USER_DEFAULT, szResPath))
555     {
556         /*
557          * Loading localized resources from INI file failed, try to load the
558          * internal resources only if they were not already loaded earlier.
559          */
560         if (!bLoadDefaultResources)
561             LoadLocalizedResourcesInternal();
562     }
563 }
564 
565 static VOID
566 FreeResources(VOID)
567 {
568     if (!pTopics)
569         return;
570 
571     while (dwNumberTopics--)
572     {
573         if (pTopics[dwNumberTopics])
574             HeapFree(GetProcessHeap(), 0, pTopics[dwNumberTopics]);
575     }
576     HeapFree(GetProcessHeap(), 0, pTopics);
577     pTopics = NULL;
578     dwNumberTopics = 0;
579 }
580 
581 #if 0
582 static VOID
583 ShowLastWin32Error(HWND hWnd)
584 {
585     LPTSTR lpMessageBuffer = NULL;
586     DWORD dwError = GetLastError();
587 
588     if (dwError == ERROR_SUCCESS)
589         return;
590 
591     if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
592                        FORMAT_MESSAGE_FROM_SYSTEM |
593                        FORMAT_MESSAGE_IGNORE_INSERTS,
594                        NULL,
595                        dwError,
596                        LANG_USER_DEFAULT,
597                        (LPTSTR)&lpMessageBuffer,
598                        0, NULL))
599     {
600         return;
601     }
602 
603     MessageBox(hWnd, lpMessageBuffer, szAppTitle, MB_OK | MB_ICONERROR);
604     LocalFree(lpMessageBuffer);
605 }
606 #endif
607 
608 int WINAPI
609 _tWinMain(HINSTANCE hInst,
610           HINSTANCE hPrevInstance,
611           LPTSTR lpszCmdLine,
612           int nCmdShow)
613 {
614     HANDLE hMutex = NULL;
615     WNDCLASSEX wndclass;
616     MSG msg;
617     HWND hWndFocus;
618     INT xPos, yPos;
619     INT xWidth, yHeight;
620     RECT rcWindow;
621     HICON hMainIcon;
622     HMENU hSystemMenu;
623     DWORD dwStyle = WS_OVERLAPPED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
624                     WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
625 
626     BITMAP BitmapInfo;
627     ULONG ulInnerWidth  = TITLE_WIDTH;
628     ULONG ulInnerHeight = (TITLE_WIDTH * 3) / 4;
629     ULONG ulTitleHeight = TITLE_HEIGHT + 3;
630 
631     UNREFERENCED_PARAMETER(hPrevInstance);
632     UNREFERENCED_PARAMETER(lpszCmdLine);
633 
634     /* Ensure only one instance is running */
635     hMutex = CreateMutex(NULL, FALSE, szWindowClass);
636     if (hMutex && (GetLastError() == ERROR_ALREADY_EXISTS))
637     {
638         /* If already started, find its window */
639         hWndMain = FindWindow(szWindowClass, NULL);
640 
641         /* Activate window */
642         ShowWindow(hWndMain, SW_SHOWNORMAL);
643         SetForegroundWindow(hWndMain);
644 
645         /* Close the mutex handle and quit */
646         CloseHandle(hMutex);
647         return 0;
648     }
649 
650     switch (GetUserDefaultUILanguage())
651     {
652         case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
653             SetProcessDefaultLayout(LAYOUT_RTL);
654             break;
655 
656         default:
657             break;
658     }
659 
660     hInstance = hInst;
661 
662     /* Load icons */
663     hMainIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN));
664 
665     /* Register the window class */
666     wndclass.cbSize = sizeof(wndclass);
667     wndclass.style = CS_HREDRAW | CS_VREDRAW;
668     wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
669     wndclass.cbClsExtra = 0;
670     wndclass.cbWndExtra = 0;
671     wndclass.hInstance = hInstance;
672     wndclass.hIcon = hMainIcon;
673     wndclass.hIconSm = NULL;
674     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
675     wndclass.hbrBackground = NULL;
676     wndclass.lpszMenuName = NULL;
677     wndclass.lpszClassName = szWindowClass;
678 
679     RegisterClassEx(&wndclass);
680 
681     /* Load the banner bitmap, and compute the window dimensions */
682     hTitleBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TITLE_BITMAP));
683     if (hTitleBitmap)
684     {
685         GetObject(hTitleBitmap, sizeof(BitmapInfo), &BitmapInfo);
686         ulInnerWidth = BitmapInfo.bmWidth;
687         ulInnerHeight = (ulInnerWidth * 3) / 4;
688         ulTitleHeight = BitmapInfo.bmHeight + 3;
689         DeleteObject(hTitleBitmap);
690     }
691     ulInnerHeight -= GetSystemMetrics(SM_CYCAPTION);
692 
693     rcWindow.top = 0;
694     rcWindow.bottom = ulInnerHeight - 1;
695     rcWindow.left = 0;
696     rcWindow.right = ulInnerWidth - 1;
697 
698     AdjustWindowRect(&rcWindow, dwStyle, FALSE);
699     xWidth  = rcWindow.right - rcWindow.left;
700     yHeight = rcWindow.bottom - rcWindow.top;
701 
702     /* Compute the window position */
703     xPos = (GetSystemMetrics(SM_CXSCREEN) - xWidth) / 2;
704     yPos = (GetSystemMetrics(SM_CYSCREEN) - yHeight) / 2;
705 
706     rcTitlePanel.top = 0;
707     rcTitlePanel.bottom = ulTitleHeight;
708     rcTitlePanel.left = 0;
709     rcTitlePanel.right = ulInnerWidth - 1;
710 
711     rcLeftPanel.top = rcTitlePanel.bottom;
712     rcLeftPanel.bottom = ulInnerHeight - 1;
713     rcLeftPanel.left = 0;
714     rcLeftPanel.right = ulInnerWidth / 3;
715 
716     rcRightPanel.top = rcLeftPanel.top;
717     rcRightPanel.bottom = rcLeftPanel.bottom;
718     rcRightPanel.left = rcLeftPanel.right;
719     rcRightPanel.right = ulInnerWidth - 1;
720 
721     /* Load the configuration and the resources */
722     LoadConfiguration();
723 
724     /* Create main window */
725     hWndMain = CreateWindow(szWindowClass,
726                             szAppTitle,
727                             dwStyle,
728                             xPos,
729                             yPos,
730                             xWidth,
731                             yHeight,
732                             0,
733                             0,
734                             hInstance,
735                             NULL);
736 
737     hSystemMenu = GetSystemMenu(hWndMain, FALSE);
738     if (hSystemMenu)
739     {
740         RemoveMenu(hSystemMenu, SC_SIZE, MF_BYCOMMAND);
741         RemoveMenu(hSystemMenu, SC_MAXIMIZE, MF_BYCOMMAND);
742     }
743 
744     ShowWindow(hWndMain, nCmdShow);
745     UpdateWindow(hWndMain);
746 
747     while (GetMessage(&msg, NULL, 0, 0) != FALSE)
748     {
749         /* Check for ENTER key presses */
750         if (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN)
751         {
752             /*
753              * The user pressed the ENTER key. Retrieve the handle to the
754              * child window that has the keyboard focus, and send it a
755              * WM_COMMAND message.
756              */
757             hWndFocus = GetFocus();
758             if (hWndFocus)
759             {
760                 SendMessage(hWndMain, WM_COMMAND,
761                             (WPARAM)GetDlgCtrlID(hWndFocus), (LPARAM)hWndFocus);
762             }
763         }
764         /* Allow using keyboard navigation */
765         else if (!IsDialogMessage(hWndMain, &msg))
766         {
767             TranslateMessage(&msg);
768             DispatchMessage(&msg);
769         }
770     }
771 
772     /* Cleanup */
773     FreeResources();
774 
775     /* Close the mutex handle and quit */
776     CloseHandle(hMutex);
777     return msg.wParam;
778 }
779 
780 
781 INT_PTR CALLBACK
782 ButtonSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
783 {
784     static WPARAM wParamOld = 0;
785     static LPARAM lParamOld = 0;
786 
787     LONG i;
788 
789     if (uMsg == WM_MOUSEMOVE)
790     {
791         /* Ignore mouse-move messages on the same point */
792         if ((wParam == wParamOld) && (lParam == lParamOld))
793             return 0;
794 
795         /* Retrieve the topic index of this button */
796         i = GetWindowLongPtr(hWnd, GWLP_ID) - TOPIC_BTN_ID_BASE;
797 
798         /*
799          * Change the focus to this button if the current topic index differs
800          * (we will receive WM_SETFOCUS afterwards).
801          */
802         if (nTopic != i)
803             SetFocus(hWnd);
804 
805         wParamOld = wParam;
806         lParamOld = lParam;
807     }
808     else if (uMsg == WM_SETFOCUS)
809     {
810         /* Retrieve the topic index of this button */
811         i = GetWindowLongPtr(hWnd, GWLP_ID) - TOPIC_BTN_ID_BASE;
812 
813         /* Change the current topic index and repaint the description panel */
814         if (nTopic != i)
815         {
816             nTopic = i;
817             InvalidateRect(hWndMain, &rcRightPanel, TRUE);
818         }
819     }
820     else if (uMsg == WM_KILLFOCUS)
821     {
822         /*
823          * We lost focus, either because the user changed button focus,
824          * or because the main window to which we belong went inactivated.
825          * If we are in the latter case, we ignore the focus change.
826          * If we are in the former case, we reset to the default topic.
827          */
828         if (GetParent(hWnd) == GetForegroundWindow())
829         {
830             nTopic = -1;
831             InvalidateRect(hWndMain, &rcRightPanel, TRUE);
832         }
833     }
834 
835     return CallWindowProc(fnOldBtn, hWnd, uMsg, wParam, lParam);
836 }
837 
838 
839 static BOOL
840 RunAction(INT nTopic)
841 {
842     PCWSTR Command = NULL, Args = NULL;
843 
844     if (nTopic < 0)
845         return TRUE;
846 
847     Command = pTopics[nTopic]->szCommand;
848     if (/* !Command && */ !*Command)
849         return TRUE;
850 
851     /* Check for known actions */
852     if (!pTopics[nTopic]->bIsCommand)
853     {
854         if (!_tcsicmp(Command, TEXT("<exit>")))
855             return FALSE;
856 
857         if (!_tcsnicmp(Command, TEXT("<msg>"), 5))
858         {
859             MessageBox(hWndMain, Command + 5, TEXT("ReactOS"), MB_OK | MB_TASKMODAL);
860             return TRUE;
861         }
862     }
863     else
864     /* Run the command */
865     {
866         Args = pTopics[nTopic]->szArgs;
867         if (!*Args) Args = NULL;
868         ShellExecute(NULL, NULL,
869                      Command, Args,
870                      NULL, SW_SHOWDEFAULT);
871     }
872 
873     return TRUE;
874 }
875 
876 
877 static DWORD
878 GetButtonHeight(HDC hDC,
879                 HFONT hFont,
880                 LPCTSTR szText,
881                 DWORD dwWidth)
882 {
883     HFONT hOldFont;
884     RECT rect;
885 
886     rect.left = 0;
887     rect.right = dwWidth - 20;
888     rect.top = 0;
889     rect.bottom = 25;
890 
891     hOldFont = (HFONT)SelectObject(hDC, hFont);
892     DrawText(hDC, szText, -1, &rect, DT_TOP | DT_CALCRECT | DT_WORDBREAK);
893     SelectObject(hDC, hOldFont);
894 
895     return (rect.bottom-rect.top + 14);
896 }
897 
898 
899 static LRESULT
900 OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
901 {
902     UINT i;
903     INT nLength;
904     HDC ScreenDC;
905     LOGFONT lf;
906     DWORD dwTop;
907     DWORD dwHeight = 0;
908     TCHAR szText[80];
909 
910     UNREFERENCED_PARAMETER(wParam);
911     UNREFERENCED_PARAMETER(lParam);
912 
913     hbrLightBlue = CreateSolidBrush(LIGHT_BLUE);
914     hbrDarkBlue  = CreateSolidBrush(DARK_BLUE);
915 
916     ZeroMemory(&lf, sizeof(lf));
917 
918     lf.lfEscapement  = 0;
919     lf.lfOrientation = 0; // TA_BASELINE;
920     // lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
921     lf.lfCharSet = ANSI_CHARSET;
922     lf.lfOutPrecision  = OUT_DEFAULT_PRECIS;
923     lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
924     lf.lfQuality = DEFAULT_QUALITY;
925     lf.lfPitchAndFamily = FF_DONTCARE;
926     if (LoadString(hInstance, IDS_FONTNAME, lf.lfFaceName, ARRAYSIZE(lf.lfFaceName)) == 0)
927         StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), TEXT("Tahoma"));
928 
929     /* Topic title font */
930     lf.lfHeight = -18;
931     lf.lfWidth  = 0;
932     lf.lfWeight = FW_NORMAL;
933     hFontTopicTitle = CreateFontIndirect(&lf);
934 
935     /* Topic description font */
936     lf.lfHeight = -11;
937     lf.lfWidth  = 0;
938     lf.lfWeight = FW_THIN;
939     hFontTopicDescription = CreateFontIndirect(&lf);
940 
941     /* Topic button font */
942     lf.lfHeight = -11;
943     lf.lfWidth  = 0;
944     lf.lfWeight = FW_BOLD;
945     hFontTopicButton = CreateFontIndirect(&lf);
946 
947     /* Load title bitmap */
948     if (hTitleBitmap)
949         hTitleBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TITLE_BITMAP));
950 
951     /* Load topic bitmaps */
952     hDefaultTopicBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_DEFAULT_TOPIC_BITMAP));
953     for (i = 0; i < dwNumberTopics; i++)
954     {
955         // FIXME: Not implemented yet!
956         // pTopics[i]->hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TOPIC_BITMAP0 + i));
957         pTopics[i]->hBitmap = NULL;
958     }
959 
960     ScreenDC = GetWindowDC(hWnd);
961     hdcMem = CreateCompatibleDC(ScreenDC);
962     ReleaseDC(hWnd, ScreenDC);
963 
964     /* Load and create the menu buttons */
965     dwTop = rcLeftPanel.top;
966     for (i = 0; i < dwNumberTopics; i++)
967     {
968         if (*pTopics[i]->szText)
969         {
970             dwHeight = GetButtonHeight(hdcMem,
971                                        hFontTopicButton,
972                                        pTopics[i]->szText,
973                                        rcLeftPanel.right - rcLeftPanel.left);
974 
975             pTopics[i]->hWndButton = CreateWindow(TEXT("BUTTON"),
976                                                   pTopics[i]->szText,
977                                                   WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_MULTILINE | BS_OWNERDRAW,
978                                                   rcLeftPanel.left,
979                                                   dwTop,
980                                                   rcLeftPanel.right - rcLeftPanel.left,
981                                                   dwHeight,
982                                                   hWnd,
983                                                   (HMENU)IntToPtr(TOPIC_BTN_ID_BASE + i), // Similar to SetWindowLongPtr(GWLP_ID)
984                                                   hInstance,
985                                                   NULL);
986             nDefaultTopic = i;
987             SendMessage(pTopics[i]->hWndButton, WM_SETFONT, (WPARAM)hFontTopicButton, MAKELPARAM(TRUE, 0));
988             fnOldBtn = (WNDPROC)SetWindowLongPtr(pTopics[i]->hWndButton, GWLP_WNDPROC, (DWORD_PTR)ButtonSubclassWndProc);
989         }
990         else
991         {
992             pTopics[i]->hWndButton = NULL;
993         }
994 
995         dwTop += dwHeight;
996     }
997 
998     /* Create the checkbox */
999     if (bDisplayCheckBox)
1000     {
1001         nLength = LoadString(hInstance, IDS_CHECKTEXT, szText, ARRAYSIZE(szText));
1002         if (nLength > 0)
1003         {
1004             lf.lfHeight = -10;
1005             lf.lfWidth  = 0;
1006             lf.lfWeight = FW_THIN;
1007             hFontCheckButton = CreateFontIndirect(&lf);
1008 
1009             hWndCheckButton = CreateWindow(TEXT("BUTTON"),
1010                                            szText,
1011                                            WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX | BS_MULTILINE /**/| BS_FLAT/**/,
1012                                            rcLeftPanel.left + 8,
1013                                            rcLeftPanel.bottom - 8 - 13,
1014                                            rcLeftPanel.right - rcLeftPanel.left - 16,
1015                                            13,
1016                                            hWnd,
1017                                            (HMENU)IDC_CHECKBUTTON,
1018                                            hInstance,
1019                                            NULL);
1020             SendMessage(hWndCheckButton, WM_SETFONT, (WPARAM)hFontCheckButton, MAKELPARAM(TRUE, 0));
1021         }
1022         else
1023         {
1024             hFontCheckButton = NULL;
1025             hWndCheckButton = NULL;
1026         }
1027     }
1028 
1029     /* Create the "Exit" button */
1030     if (bDisplayExitBtn)
1031     {
1032         nLength = LoadString(hInstance, IDS_CLOSETEXT, szText, ARRAYSIZE(szText));
1033         if (nLength > 0)
1034         {
1035             hWndCloseButton = CreateWindow(TEXT("BUTTON"),
1036                                            szText,
1037                                            WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_FLAT,
1038                                            rcRightPanel.right - 8 - 57,
1039                                            rcRightPanel.bottom - 8 - 21,
1040                                            57,
1041                                            21,
1042                                            hWnd,
1043                                            (HMENU)IDC_CLOSEBUTTON,
1044                                            hInstance,
1045                                            NULL);
1046             nDefaultTopic = -1;
1047             SendMessage(hWndCloseButton, WM_SETFONT, (WPARAM)hFontTopicButton, MAKELPARAM(TRUE, 0));
1048         }
1049         else
1050         {
1051             hWndCloseButton = NULL;
1052         }
1053     }
1054 
1055     return 0;
1056 }
1057 
1058 
1059 static LRESULT
1060 OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
1061 {
1062     UNREFERENCED_PARAMETER(lParam);
1063 
1064     /* Retrieve the low-word from wParam */
1065     wParam = LOWORD(wParam);
1066 
1067     /* Execute action */
1068     if (wParam == IDC_CLOSEBUTTON)
1069     {
1070         DestroyWindow(hWnd);
1071     }
1072     else if (wParam - TOPIC_BTN_ID_BASE < dwNumberTopics)
1073     {
1074         if (RunAction(wParam - TOPIC_BTN_ID_BASE) == FALSE)
1075             DestroyWindow(hWnd); // Corresponds to a <exit> action.
1076     }
1077 
1078     return 0;
1079 }
1080 
1081 
1082 static VOID
1083 PaintBanner(HDC hdc, LPRECT rcPanel)
1084 {
1085     HBITMAP hOldBitmap;
1086     HBRUSH hOldBrush;
1087 
1088     /* Title bitmap */
1089     hOldBitmap = (HBITMAP)SelectObject(hdcMem, hTitleBitmap);
1090     BitBlt(hdc,
1091            rcPanel->left,
1092            rcPanel->top,
1093            rcPanel->right - rcPanel->left,
1094            rcPanel->bottom - 3,
1095            hdcMem, 0, 0, SRCCOPY);
1096     SelectObject(hdcMem, hOldBitmap);
1097 
1098     /* Dark blue line */
1099     hOldBrush = (HBRUSH)SelectObject(hdc, hbrDarkBlue);
1100     PatBlt(hdc,
1101            rcPanel->left,
1102            rcPanel->bottom - 3,
1103            rcPanel->right - rcPanel->left,
1104            3,
1105            PATCOPY);
1106     SelectObject(hdc, hOldBrush);
1107 }
1108 
1109 
1110 static LRESULT
1111 OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
1112 {
1113     HPEN hPen;
1114     HPEN hOldPen;
1115     HDC hdc;
1116     PAINTSTRUCT ps;
1117     HBITMAP hOldBitmap = NULL;
1118     HBRUSH hOldBrush;
1119     HFONT hOldFont;
1120     RECT rcTitle, rcDescription;
1121     BITMAP bmpInfo;
1122     TCHAR szVersion[50];
1123     LPTSTR lpTitle = NULL, lpDesc = NULL;
1124 
1125     UNREFERENCED_PARAMETER(wParam);
1126     UNREFERENCED_PARAMETER(lParam);
1127 
1128     hdc = BeginPaint(hWnd, &ps);
1129 
1130     /* Banner panel */
1131     PaintBanner(hdc, &rcTitlePanel);
1132 
1133     /* Left panel */
1134     hOldBrush = (HBRUSH)SelectObject(hdc, hbrLightBlue);
1135     PatBlt(hdc,
1136            rcLeftPanel.left,
1137            rcLeftPanel.top,
1138            rcLeftPanel.right - rcLeftPanel.left,
1139            rcLeftPanel.bottom - rcLeftPanel.top,
1140            PATCOPY);
1141     SelectObject(hdc, hOldBrush);
1142 
1143     /* Right panel */
1144     hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(WHITE_BRUSH));
1145     PatBlt(hdc,
1146            rcRightPanel.left,
1147            rcRightPanel.top,
1148            rcRightPanel.right - rcRightPanel.left,
1149            rcRightPanel.bottom - rcRightPanel.top,
1150            PATCOPY);
1151     SelectObject(hdc, hOldBrush);
1152 
1153     /* Draw dark vertical line */
1154     hPen = CreatePen(PS_SOLID, 0, DARK_BLUE);
1155     hOldPen = (HPEN)SelectObject(hdc, hPen);
1156     MoveToEx(hdc, rcRightPanel.left, rcRightPanel.top, NULL);
1157     LineTo(hdc, rcRightPanel.left, rcRightPanel.bottom);
1158     SelectObject(hdc, hOldPen);
1159     DeleteObject(hPen);
1160 
1161     /* Draw topic bitmap */
1162     if ((nTopic == -1) && (hDefaultTopicBitmap))
1163     {
1164         GetObject(hDefaultTopicBitmap, sizeof(bmpInfo), &bmpInfo);
1165         hOldBitmap = (HBITMAP)SelectObject(hdcMem, hDefaultTopicBitmap);
1166         BitBlt(hdc,
1167                rcRightPanel.right - bmpInfo.bmWidth,
1168                rcRightPanel.bottom - bmpInfo.bmHeight,
1169                bmpInfo.bmWidth,
1170                bmpInfo.bmHeight,
1171                hdcMem,
1172                0,
1173                0,
1174                SRCCOPY);
1175     }
1176     else if ((nTopic != -1) && (pTopics[nTopic]->hBitmap))
1177     {
1178         GetObject(pTopics[nTopic]->hBitmap, sizeof(bmpInfo), &bmpInfo);
1179         hOldBitmap = (HBITMAP)SelectObject(hdcMem, pTopics[nTopic]->hBitmap);
1180         BitBlt(hdc,
1181                rcRightPanel.right - bmpInfo.bmWidth,
1182                rcRightPanel.bottom - bmpInfo.bmHeight,
1183                bmpInfo.bmWidth,
1184                bmpInfo.bmHeight,
1185                hdcMem,
1186                0,
1187                0,
1188                SRCCOPY);
1189     }
1190 
1191     if (nTopic == -1)
1192     {
1193         lpTitle = szDefaultTitle;
1194         lpDesc  = szDefaultDesc;
1195     }
1196     else
1197     {
1198         lpTitle = pTopics[nTopic]->szTitle;
1199         lpDesc  = pTopics[nTopic]->szDesc;
1200     }
1201 
1202     SetBkMode(hdc, TRANSPARENT);
1203 
1204     /* Draw version information */
1205     StringCchCopy(szVersion, ARRAYSIZE(szVersion),
1206                   TEXT("ReactOS ") TEXT(KERNEL_VERSION_STR));
1207 
1208     /*
1209      * Compute the original rect (position & size) of the version info,
1210      * depending whether the checkbox is displayed (version info in the
1211      * right panel) or not (version info in the left panel).
1212      */
1213     if (bDisplayCheckBox)
1214         rcTitle = rcRightPanel;
1215     else
1216         rcTitle = rcLeftPanel;
1217 
1218     rcTitle.left   = rcTitle.left + 8;
1219     rcTitle.right  = rcTitle.right - 5;
1220     rcTitle.top    = rcTitle.bottom - 43;
1221     rcTitle.bottom = rcTitle.bottom - 8;
1222 
1223     hOldFont = (HFONT)SelectObject(hdc, hFontTopicDescription);
1224     DrawText(hdc, szVersion, -1, &rcTitle, DT_BOTTOM | DT_CALCRECT | DT_SINGLELINE);
1225     SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
1226     DrawText(hdc, szVersion, -1, &rcTitle, DT_BOTTOM | DT_SINGLELINE);
1227     SelectObject(hdc, hOldFont);
1228 
1229     /* Draw topic title */
1230     rcTitle.left = rcRightPanel.left + 12;
1231     rcTitle.right = rcRightPanel.right - 8;
1232     rcTitle.top = rcRightPanel.top + 8;
1233     rcTitle.bottom = rcTitle.top + 57;
1234     hOldFont = (HFONT)SelectObject(hdc, hFontTopicTitle);
1235     DrawText(hdc, lpTitle, -1, &rcTitle, DT_TOP | DT_CALCRECT);
1236     SetTextColor(hdc, DARK_BLUE);
1237     DrawText(hdc, lpTitle, -1, &rcTitle, DT_TOP);
1238     SelectObject(hdc, hOldFont);
1239 
1240     /* Draw topic description */
1241     rcDescription.left = rcRightPanel.left + 12;
1242     rcDescription.right = rcRightPanel.right - 8;
1243     rcDescription.top = rcTitle.bottom + 8;
1244     rcDescription.bottom = rcRightPanel.bottom - 20;
1245     hOldFont = (HFONT)SelectObject(hdc, hFontTopicDescription);
1246     SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
1247     DrawText(hdc, lpDesc, -1, &rcDescription, DT_TOP | DT_WORDBREAK);
1248     SelectObject(hdc, hOldFont);
1249 
1250     SetBkMode(hdc, OPAQUE);
1251 
1252     SelectObject(hdcMem, hOldBrush);
1253     SelectObject(hdcMem, hOldBitmap);
1254 
1255     EndPaint(hWnd, &ps);
1256 
1257     return 0;
1258 }
1259 
1260 
1261 static LRESULT
1262 OnDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
1263 {
1264     LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam;
1265     HPEN hPen, hOldPen;
1266     HBRUSH hOldBrush;
1267     INT iBkMode;
1268     TCHAR szText[80];
1269 
1270     UNREFERENCED_PARAMETER(hWnd);
1271     UNREFERENCED_PARAMETER(wParam);
1272 
1273 #if 0
1274     /* Neither the checkbox button nor the close button implement owner-drawing */
1275     if (lpDis->hwndItem == hWndCheckButton)
1276         return 0;
1277     if (lpDis->hwndItem == hWndCloseButton)
1278     {
1279         DrawFrameControl(lpDis->hDC,
1280                          &lpDis->rcItem,
1281                          DFC_BUTTON,
1282                          DFCS_BUTTONPUSH | DFCS_FLAT);
1283         return TRUE;
1284     }
1285 #endif
1286 
1287     if (lpDis->CtlID == (ULONG)(TOPIC_BTN_ID_BASE + nTopic))
1288         hOldBrush = (HBRUSH)SelectObject(lpDis->hDC, GetStockObject(WHITE_BRUSH));
1289     else
1290         hOldBrush = (HBRUSH)SelectObject(lpDis->hDC, hbrLightBlue);
1291 
1292     PatBlt(lpDis->hDC,
1293            lpDis->rcItem.left,
1294            lpDis->rcItem.top,
1295            lpDis->rcItem.right,
1296            lpDis->rcItem.bottom,
1297            PATCOPY);
1298     SelectObject(lpDis->hDC, hOldBrush);
1299 
1300     hPen = CreatePen(PS_SOLID, 0, DARK_BLUE);
1301     hOldPen = (HPEN)SelectObject(lpDis->hDC, hPen);
1302     MoveToEx(lpDis->hDC, lpDis->rcItem.left, lpDis->rcItem.bottom - 1, NULL);
1303     LineTo(lpDis->hDC, lpDis->rcItem.right, lpDis->rcItem.bottom - 1);
1304     SelectObject(lpDis->hDC, hOldPen);
1305     DeleteObject(hPen);
1306 
1307     InflateRect(&lpDis->rcItem, -10, -4);
1308     OffsetRect(&lpDis->rcItem, 0, 1);
1309     GetWindowText(lpDis->hwndItem, szText, ARRAYSIZE(szText));
1310     SetTextColor(lpDis->hDC, GetSysColor(COLOR_WINDOWTEXT));
1311     iBkMode = SetBkMode(lpDis->hDC, TRANSPARENT);
1312     DrawText(lpDis->hDC, szText, -1, &lpDis->rcItem, DT_TOP | DT_LEFT | DT_WORDBREAK);
1313     SetBkMode(lpDis->hDC, iBkMode);
1314 
1315     return TRUE;
1316 }
1317 
1318 
1319 static LRESULT
1320 OnMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
1321 {
1322     static WPARAM wParamOld = 0;
1323     static LPARAM lParamOld = 0;
1324 
1325     /* Ignore mouse-move messages on the same point */
1326     if ((wParam == wParamOld) && (lParam == lParamOld))
1327         return 0;
1328 
1329     /*
1330      * If the user moves the mouse over the main window, outside of the
1331      * topic buttons, reset the current topic to the default one and
1332      * change the focus to some other default button (to keep keyboard
1333      * navigation possible).
1334      */
1335     if (nTopic != -1)
1336     {
1337         INT nOldTopic = nTopic;
1338         nTopic = -1;
1339         /* Also repaint the buttons, otherwise nothing repaints... */
1340         InvalidateRect(pTopics[nOldTopic]->hWndButton, NULL, TRUE);
1341 
1342         /* Set the focus to some other default button */
1343         if (hWndCheckButton)
1344             SetFocus(hWndCheckButton);
1345         else if (hWndCloseButton)
1346             SetFocus(hWndCloseButton);
1347         // SetFocus(hWnd);
1348 
1349         /* Repaint the description panel */
1350         InvalidateRect(hWndMain, &rcRightPanel, TRUE);
1351     }
1352 
1353     wParamOld = wParam;
1354     lParamOld = lParam;
1355 
1356     return 0;
1357 }
1358 
1359 
1360 static LRESULT
1361 OnCtlColorStatic(HWND hWnd, WPARAM wParam, LPARAM lParam)
1362 {
1363     UNREFERENCED_PARAMETER(hWnd);
1364 
1365     if ((HWND)lParam == hWndCheckButton)
1366     {
1367         SetBkMode((HDC)wParam, TRANSPARENT);
1368         return (LRESULT)hbrLightBlue;
1369     }
1370 
1371     return 0;
1372 }
1373 
1374 
1375 static LRESULT
1376 OnActivate(HWND hWnd, WPARAM wParam, LPARAM lParam)
1377 {
1378     UNREFERENCED_PARAMETER(hWnd);
1379     UNREFERENCED_PARAMETER(lParam);
1380 
1381     if (wParam != WA_INACTIVE)
1382     {
1383         /*
1384          * The main window is re-activated, set the focus back to
1385          * either the current topic or a default button.
1386          */
1387         if (nTopic != -1)
1388             SetFocus(pTopics[nTopic]->hWndButton);
1389         else if (hWndCheckButton)
1390             SetFocus(hWndCheckButton);
1391         else if (hWndCloseButton)
1392             SetFocus(hWndCloseButton);
1393 
1394         // InvalidateRect(hWndMain, &rcRightPanel, TRUE);
1395     }
1396 
1397     return 0;
1398 }
1399 
1400 
1401 static LRESULT
1402 OnDestroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
1403 {
1404     UINT i;
1405 
1406     UNREFERENCED_PARAMETER(hWnd);
1407     UNREFERENCED_PARAMETER(wParam);
1408     UNREFERENCED_PARAMETER(lParam);
1409 
1410     for (i = 0; i < dwNumberTopics; i++)
1411     {
1412         if (pTopics[i]->hWndButton)
1413             DestroyWindow(pTopics[i]->hWndButton);
1414     }
1415 
1416     if (hWndCloseButton)
1417         DestroyWindow(hWndCloseButton);
1418 
1419     if (hWndCheckButton)
1420         DestroyWindow(hWndCheckButton);
1421 
1422     DeleteDC(hdcMem);
1423 
1424     /* Delete bitmaps */
1425     DeleteObject(hDefaultTopicBitmap);
1426     DeleteObject(hTitleBitmap);
1427     for (i = 0; i < dwNumberTopics; i++)
1428     {
1429         if (pTopics[i]->hBitmap)
1430             DeleteObject(pTopics[i]->hBitmap);
1431     }
1432 
1433     DeleteObject(hFontTopicTitle);
1434     DeleteObject(hFontTopicDescription);
1435     DeleteObject(hFontTopicButton);
1436 
1437     if (hFontCheckButton)
1438         DeleteObject(hFontCheckButton);
1439 
1440     DeleteObject(hbrLightBlue);
1441     DeleteObject(hbrDarkBlue);
1442 
1443     return 0;
1444 }
1445 
1446 
1447 INT_PTR CALLBACK
1448 MainWndProc(HWND hWnd,
1449             UINT uMsg,
1450             WPARAM wParam,
1451             LPARAM lParam)
1452 {
1453     switch (uMsg)
1454     {
1455         case WM_CREATE:
1456             return OnCreate(hWnd, wParam, lParam);
1457 
1458         case WM_COMMAND:
1459             return OnCommand(hWnd, wParam, lParam);
1460 
1461         case WM_ACTIVATE:
1462             return OnActivate(hWnd, wParam, lParam);
1463 
1464         case WM_PAINT:
1465             return OnPaint(hWnd, wParam, lParam);
1466 
1467         case WM_DRAWITEM:
1468             return OnDrawItem(hWnd, wParam, lParam);
1469 
1470         case WM_CTLCOLORSTATIC:
1471             return OnCtlColorStatic(hWnd, wParam, lParam);
1472 
1473         case WM_MOUSEMOVE:
1474             return OnMouseMove(hWnd, wParam, lParam);
1475 
1476         case WM_DESTROY:
1477             OnDestroy(hWnd, wParam, lParam);
1478             PostQuitMessage(0);
1479             return 0;
1480     }
1481 
1482     return DefWindowProc(hWnd, uMsg, wParam, lParam);
1483 }
1484 
1485 /* EOF */
1486