mirror of
https://github.com/reactos/reactos.git
synced 2024-08-26 13:18:51 +00:00
![Hermès Bélusca-Maïto](/assets/img/avatar_default.png)
- Don't hardcode the installer file name in ExpandInstallerPath() but provide it from a parameter. Return TRUE/FALSE if the installer file is/is not found. - Expand any environment string present in the items command line. - Enable/disable the menu buttons depending on whether or not an associated command is present. - Enable mirroring from resources.
1636 lines
49 KiB
C
1636 lines
49 KiB
C
/*
|
|
* ReactOS applications
|
|
* Copyright (C) 2001, 2002, 2003 ReactOS Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS "Welcome"/AutoRun application
|
|
* FILE: base/setup/welcome/welcome.c
|
|
* PROGRAMMERS: Eric Kohl
|
|
* Casper S. Hornstrup (chorns@users.sourceforge.net)
|
|
* Hermes Belusca-Maito
|
|
*
|
|
* NOTE:
|
|
* This utility can be customized by using localized INI configuration files.
|
|
* The default strings are stored in the utility's resources.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <tchar.h>
|
|
|
|
#include <windef.h>
|
|
#include <winbase.h>
|
|
#include <wingdi.h>
|
|
#include <winnls.h>
|
|
#include <winuser.h>
|
|
#include <shellapi.h>
|
|
#include <strsafe.h>
|
|
|
|
#include <reactos/buildno.h>
|
|
|
|
#include "resource.h"
|
|
|
|
#define LIGHT_BLUE RGB(214, 239, 247)
|
|
#define DARK_BLUE RGB(107, 123, 140)
|
|
|
|
#define TITLE_WIDTH 480
|
|
#define TITLE_HEIGHT 93
|
|
|
|
/* GLOBALS ******************************************************************/
|
|
|
|
TCHAR szWindowClass[] = TEXT("WelcomeWindowClass");
|
|
TCHAR szAppTitle[80];
|
|
|
|
HINSTANCE hInstance;
|
|
|
|
HWND hWndMain = NULL;
|
|
|
|
HWND hWndCheckButton = NULL;
|
|
HWND hWndCloseButton = NULL;
|
|
|
|
BOOL bDisplayCheckBox = FALSE;
|
|
BOOL bDisplayExitBtn = TRUE;
|
|
|
|
#define BUFFER_SIZE 1024
|
|
|
|
#define TOPIC_TITLE_LENGTH 80
|
|
#define TOPIC_DESC_LENGTH 1024
|
|
|
|
typedef struct _TOPIC
|
|
{
|
|
HBITMAP hBitmap;
|
|
HWND hWndButton;
|
|
|
|
/*
|
|
* TRUE : szCommand contains a command (e.g. executable to run);
|
|
* FALSE: szCommand contains a custom "Welcome"/AutoRun action.
|
|
*/
|
|
BOOL bIsCommand;
|
|
|
|
TCHAR szText[80];
|
|
TCHAR szTitle[TOPIC_TITLE_LENGTH];
|
|
TCHAR szDesc[TOPIC_DESC_LENGTH];
|
|
TCHAR szCommand[512];
|
|
TCHAR szArgs[512];
|
|
} TOPIC, *PTOPIC;
|
|
|
|
DWORD dwNumberTopics = 0;
|
|
PTOPIC* pTopics = NULL;
|
|
|
|
WNDPROC fnOldBtn;
|
|
|
|
TCHAR szDefaultTitle[TOPIC_TITLE_LENGTH];
|
|
TCHAR szDefaultDesc[TOPIC_DESC_LENGTH];
|
|
|
|
#define TOPIC_BTN_ID_BASE 100
|
|
|
|
INT nTopic = -1; // Active (focused) topic
|
|
INT nDefaultTopic = -1; // Default selected topic
|
|
|
|
HDC hdcMem = NULL;
|
|
HBITMAP hTitleBitmap = NULL;
|
|
HBITMAP hDefaultTopicBitmap = NULL;
|
|
|
|
HFONT hFontTopicButton;
|
|
HFONT hFontTopicTitle;
|
|
HFONT hFontTopicDescription;
|
|
HFONT hFontCheckButton;
|
|
|
|
HBRUSH hbrLightBlue;
|
|
HBRUSH hbrDarkBlue;
|
|
|
|
RECT rcTitlePanel;
|
|
RECT rcLeftPanel;
|
|
RECT rcRightPanel;
|
|
|
|
|
|
INT_PTR CALLBACK
|
|
MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
|
|
/* FUNCTIONS ****************************************************************/
|
|
|
|
INT GetLocaleName(IN LCID Locale, OUT LPTSTR lpLCData, IN SIZE_T cchData)
|
|
{
|
|
INT cchRet, cchRet2;
|
|
|
|
/* Try to retrieve the locale language name (LOCALE_SNAME is supported on Vista+) */
|
|
cchRet = GetLocaleInfo(Locale, LOCALE_SNAME, lpLCData, cchData);
|
|
if (cchRet || (GetLastError() != ERROR_INVALID_FLAGS))
|
|
return cchRet;
|
|
|
|
/*
|
|
* We failed because LOCALE_SNAME was unrecognized, so try to manually build
|
|
* a language name in the form xx-YY (WARNING: this method has its limitations).
|
|
*/
|
|
cchRet = GetLocaleInfo(Locale, LOCALE_SISO639LANGNAME, lpLCData, cchData);
|
|
if (cchRet <= 1)
|
|
return cchRet;
|
|
|
|
lpLCData += (cchRet - 1);
|
|
cchData -= (cchRet - 1);
|
|
if (cchData <= 1)
|
|
return cchRet;
|
|
|
|
/* Try to get the second part; we add the '-' separator only if we succeed */
|
|
cchRet2 = GetLocaleInfo(Locale, LOCALE_SISO3166CTRYNAME, lpLCData + 1, cchData - 1);
|
|
if (cchRet2 <= 1)
|
|
return cchRet;
|
|
cchRet += cchRet2; // 'cchRet' already counts '-'.
|
|
|
|
*lpLCData = _T('-');
|
|
|
|
return cchRet;
|
|
}
|
|
|
|
VOID TranslateEscapes(IN OUT LPTSTR lpString)
|
|
{
|
|
LPTSTR pEscape = NULL; // Next backslash escape sequence.
|
|
|
|
while (lpString && *lpString)
|
|
{
|
|
/* Find the next backslash escape sequence */
|
|
pEscape = _tcschr(lpString, _T('\\'));
|
|
if (!pEscape)
|
|
break;
|
|
|
|
/* Go past the escape backslash */
|
|
lpString = pEscape + 1;
|
|
|
|
/* Find which sequence it is */
|
|
switch (*lpString)
|
|
{
|
|
case _T('\0'):
|
|
// *pEscape = _T('\0'); // Enable if one wants to convert \<NULL> into <NULL>.
|
|
// lpString = pEscape + 1; // Loop will stop at the next iteration.
|
|
break;
|
|
|
|
/* New-line and carriage return */
|
|
case _T('n'): case _T('r'):
|
|
// case _T('\\'): // others?
|
|
// So far we only need to deal with the newlines.
|
|
{
|
|
if (*lpString == _T('n'))
|
|
*pEscape = _T('\n');
|
|
else if (*lpString == _T('r'))
|
|
*pEscape = _T('\r');
|
|
|
|
memmove(lpString, lpString + 1, (_tcslen(lpString + 1) + 1) * sizeof(TCHAR));
|
|
break;
|
|
}
|
|
|
|
/* \xhhhh hexadecimal character specification */
|
|
case _T('x'):
|
|
{
|
|
LPTSTR lpStringNew;
|
|
*pEscape = (WCHAR)_tcstoul(lpString + 1, &lpStringNew, 16);
|
|
memmove(lpString, lpStringNew, (_tcslen(lpStringNew) + 1) * sizeof(TCHAR));
|
|
break;
|
|
}
|
|
|
|
/* Unknown escape sequence, ignore it */
|
|
default:
|
|
lpString++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Expands the path for the ReactOS Installer "reactos.exe".
|
|
* See also base/system/userinit/userinit.c!StartInstaller()
|
|
*/
|
|
BOOL
|
|
ExpandInstallerPath(
|
|
IN LPCTSTR lpInstallerName,
|
|
OUT LPTSTR lpInstallerPath,
|
|
IN SIZE_T PathSize)
|
|
{
|
|
SYSTEM_INFO SystemInfo;
|
|
SIZE_T cchInstallerNameLen;
|
|
PTSTR ptr;
|
|
DWORD dwAttribs;
|
|
|
|
cchInstallerNameLen = _tcslen(lpInstallerName);
|
|
if (PathSize < cchInstallerNameLen)
|
|
{
|
|
/* The buffer is not large enough to contain the installer file name */
|
|
*lpInstallerPath = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* First, try to find the installer using the default drive, under
|
|
* the directory whose name corresponds to the currently-running
|
|
* CPU architecture.
|
|
*/
|
|
GetSystemInfo(&SystemInfo);
|
|
|
|
*lpInstallerPath = 0;
|
|
GetModuleFileName(NULL, lpInstallerPath, PathSize - cchInstallerNameLen - 1);
|
|
ptr = _tcschr(lpInstallerPath, _T('\\'));
|
|
if (ptr)
|
|
*++ptr = 0;
|
|
else
|
|
*lpInstallerPath = 0;
|
|
|
|
/* Append the corresponding CPU architecture */
|
|
switch (SystemInfo.wProcessorArchitecture)
|
|
{
|
|
case PROCESSOR_ARCHITECTURE_INTEL:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("I386"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_MIPS:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("MIPS"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_ALPHA:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("ALPHA"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_PPC:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("PPC"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_SHX:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("SHX"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_ARM:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("ARM"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_IA64:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("IA64"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_ALPHA64:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("ALPHA64"));
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_AMD64:
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("AMD64"));
|
|
break;
|
|
|
|
// case PROCESSOR_ARCHITECTURE_MSIL: /* .NET CPU-independent code */
|
|
case PROCESSOR_ARCHITECTURE_UNKNOWN:
|
|
default:
|
|
SystemInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
if (SystemInfo.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_UNKNOWN)
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("\\"));
|
|
StringCchCat(lpInstallerPath, PathSize, lpInstallerName);
|
|
|
|
dwAttribs = GetFileAttributes(lpInstallerPath);
|
|
if ((dwAttribs != INVALID_FILE_ATTRIBUTES) &&
|
|
!(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
/* We have found the installer */
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* We failed. Try to find the installer from either the current
|
|
* ReactOS installation directory, or from our current directory.
|
|
*/
|
|
*lpInstallerPath = 0;
|
|
if (GetWindowsDirectory(lpInstallerPath, PathSize - cchInstallerNameLen - 1))
|
|
StringCchCat(lpInstallerPath, PathSize, TEXT("\\"));
|
|
StringCchCat(lpInstallerPath, PathSize, lpInstallerName);
|
|
|
|
dwAttribs = GetFileAttributes(lpInstallerPath);
|
|
if ((dwAttribs != INVALID_FILE_ATTRIBUTES) &&
|
|
!(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
/* We have found the installer */
|
|
return TRUE;
|
|
}
|
|
|
|
/* Installer not found */
|
|
*lpInstallerPath = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
VOID InitializeTopicList(VOID)
|
|
{
|
|
dwNumberTopics = 0;
|
|
pTopics = NULL;
|
|
}
|
|
|
|
PTOPIC AddNewTopic(VOID)
|
|
{
|
|
PTOPIC pTopic, *pTopicsTmp;
|
|
|
|
/* Allocate (or reallocate) the list of topics */
|
|
if (!pTopics)
|
|
pTopicsTmp = HeapAlloc(GetProcessHeap(), 0, (dwNumberTopics + 1) * sizeof(*pTopics));
|
|
else
|
|
pTopicsTmp = HeapReAlloc(GetProcessHeap(), 0, pTopics, (dwNumberTopics + 1) * sizeof(*pTopics));
|
|
if (!pTopicsTmp)
|
|
return NULL; // Cannot reallocate more
|
|
pTopics = pTopicsTmp;
|
|
|
|
/* Allocate a new topic entry */
|
|
pTopic = HeapAlloc(GetProcessHeap(), 0, sizeof(*pTopic));
|
|
if (!pTopic)
|
|
return NULL; // Cannot reallocate more
|
|
pTopics[dwNumberTopics++] = pTopic;
|
|
|
|
/* Return the allocated topic entry */
|
|
return pTopic;
|
|
}
|
|
|
|
PTOPIC
|
|
AddNewTopicEx(
|
|
IN LPTSTR szText OPTIONAL,
|
|
IN LPTSTR szTitle OPTIONAL,
|
|
IN LPTSTR szDesc OPTIONAL,
|
|
IN LPTSTR szCommand OPTIONAL,
|
|
IN LPTSTR szArgs OPTIONAL,
|
|
IN LPTSTR szAction OPTIONAL)
|
|
{
|
|
PTOPIC pTopic = AddNewTopic();
|
|
if (!pTopic)
|
|
return NULL;
|
|
|
|
if (szText && *szText)
|
|
StringCchCopy(pTopic->szText, ARRAYSIZE(pTopic->szText), szText);
|
|
else
|
|
*pTopic->szText = 0;
|
|
|
|
if (szTitle && *szTitle)
|
|
StringCchCopy(pTopic->szTitle, ARRAYSIZE(pTopic->szTitle), szTitle);
|
|
else
|
|
*pTopic->szTitle = 0;
|
|
|
|
if (szDesc && *szDesc)
|
|
{
|
|
StringCchCopy(pTopic->szDesc, ARRAYSIZE(pTopic->szDesc), szDesc);
|
|
TranslateEscapes(pTopic->szDesc);
|
|
}
|
|
else
|
|
{
|
|
*pTopic->szDesc = 0;
|
|
}
|
|
|
|
if (szCommand && *szCommand)
|
|
{
|
|
pTopic->bIsCommand = TRUE;
|
|
|
|
/* Check for special applications: ReactOS Installer */
|
|
if (_tcsicmp(szCommand, TEXT("reactos.exe")) == 0)
|
|
{
|
|
ExpandInstallerPath(szCommand, pTopic->szCommand, ARRAYSIZE(pTopic->szCommand));
|
|
}
|
|
else
|
|
{
|
|
/* Expand any environment string in the command line */
|
|
DWORD dwSize = ExpandEnvironmentStringsW(szCommand, NULL, 0);
|
|
if (dwSize <= ARRAYSIZE(pTopic->szCommand))
|
|
ExpandEnvironmentStringsW(szCommand, pTopic->szCommand, ARRAYSIZE(pTopic->szCommand));
|
|
else
|
|
StringCchCopy(pTopic->szCommand, ARRAYSIZE(pTopic->szCommand), szCommand);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pTopic->bIsCommand = FALSE;
|
|
*pTopic->szCommand = 0;
|
|
}
|
|
|
|
/* Only care about command arguments if we actually have a command */
|
|
if (*pTopic->szCommand)
|
|
{
|
|
if (szArgs && *szArgs)
|
|
{
|
|
StringCchCopy(pTopic->szArgs, ARRAYSIZE(pTopic->szArgs), szArgs);
|
|
}
|
|
else
|
|
{
|
|
/* Check for special applications: ReactOS Shell */
|
|
if (/* pTopic->szCommand && */ *pTopic->szCommand &&
|
|
_tcsicmp(pTopic->szCommand, TEXT("explorer.exe")) == 0)
|
|
{
|
|
#if 0
|
|
TCHAR CurrentDir[MAX_PATH];
|
|
GetCurrentDirectory(ARRAYSIZE(CurrentDir), CurrentDir);
|
|
#endif
|
|
StringCchCopy(pTopic->szArgs, ARRAYSIZE(pTopic->szArgs), TEXT("\\"));
|
|
}
|
|
else
|
|
{
|
|
*pTopic->szArgs = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pTopic->szArgs = 0;
|
|
}
|
|
|
|
/* Only care about custom actions if we actually don't have a command */
|
|
if (!*pTopic->szCommand && szAction && *szAction)
|
|
{
|
|
/*
|
|
* Re-use the pTopic->szCommand member. We distinguish with respect to
|
|
* a regular command by using the pTopic->bIsCommand flag.
|
|
*/
|
|
pTopic->bIsCommand = FALSE;
|
|
StringCchCopy(pTopic->szCommand, ARRAYSIZE(pTopic->szCommand), szAction);
|
|
TranslateEscapes(pTopic->szCommand);
|
|
}
|
|
|
|
return pTopic;
|
|
}
|
|
|
|
static VOID
|
|
LoadLocalizedResourcesInternal(VOID)
|
|
{
|
|
#define MAX_NUMBER_INTERNAL_TOPICS 3
|
|
|
|
UINT i;
|
|
LPTSTR lpszCommand, lpszAction;
|
|
TOPIC newTopic, *pTopic;
|
|
|
|
for (i = 0; i < MAX_NUMBER_INTERNAL_TOPICS; ++i)
|
|
{
|
|
lpszCommand = NULL, lpszAction = NULL;
|
|
|
|
/* Retrieve the information */
|
|
if (!LoadString(hInstance, IDS_TOPIC_BUTTON0 + i, newTopic.szText, ARRAYSIZE(newTopic.szText)))
|
|
*newTopic.szText = 0;
|
|
if (!LoadString(hInstance, IDS_TOPIC_TITLE0 + i, newTopic.szTitle, ARRAYSIZE(newTopic.szTitle)))
|
|
*newTopic.szTitle = 0;
|
|
if (!LoadString(hInstance, IDS_TOPIC_DESC0 + i, newTopic.szDesc, ARRAYSIZE(newTopic.szDesc)))
|
|
*newTopic.szDesc = 0;
|
|
|
|
if (!LoadString(hInstance, IDS_TOPIC_COMMAND0 + i, newTopic.szCommand, ARRAYSIZE(newTopic.szCommand)))
|
|
*newTopic.szCommand = 0;
|
|
|
|
/* Only care about command arguments if we actually have a command */
|
|
if (*newTopic.szCommand)
|
|
{
|
|
lpszCommand = newTopic.szCommand;
|
|
if (!LoadString(hInstance, IDS_TOPIC_CMD_ARGS0 + i, newTopic.szArgs, ARRAYSIZE(newTopic.szArgs)))
|
|
*newTopic.szArgs = 0;
|
|
}
|
|
/* Only care about custom actions if we actually don't have a command */
|
|
else // if (!*newTopic.szCommand)
|
|
{
|
|
lpszAction = newTopic.szCommand;
|
|
if (!LoadString(hInstance, IDS_TOPIC_ACTION0 + i, newTopic.szCommand, ARRAYSIZE(newTopic.szCommand)))
|
|
*newTopic.szCommand = 0;
|
|
}
|
|
|
|
/* Allocate a new topic */
|
|
pTopic = AddNewTopicEx(newTopic.szText,
|
|
newTopic.szTitle,
|
|
newTopic.szDesc,
|
|
lpszCommand,
|
|
newTopic.szArgs,
|
|
lpszAction);
|
|
if (!pTopic)
|
|
break; // Cannot reallocate more
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
LoadLocalizedResourcesFromINI(LCID Locale, LPTSTR lpResPath)
|
|
{
|
|
DWORD dwRet;
|
|
DWORD dwAttribs;
|
|
DWORD dwSize;
|
|
TCHAR szBuffer[LOCALE_NAME_MAX_LENGTH];
|
|
TCHAR szIniPath[MAX_PATH];
|
|
LPTSTR lpszSections = NULL, lpszSection = NULL;
|
|
LPTSTR lpszCommand, lpszAction;
|
|
TOPIC newTopic, *pTopic;
|
|
|
|
/* Retrieve the locale name (on which the INI file name is based) */
|
|
dwRet = (DWORD)GetLocaleName(Locale, szBuffer, ARRAYSIZE(szBuffer));
|
|
if (!dwRet)
|
|
{
|
|
/* Fall back to english (US) */
|
|
StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), TEXT("en-US"));
|
|
}
|
|
|
|
/* Build the INI file name */
|
|
StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath),
|
|
TEXT("%s\\%s.ini"), lpResPath, szBuffer);
|
|
|
|
/* Verify that the file exists, otherwise fall back to english (US) */
|
|
dwAttribs = GetFileAttributes(szIniPath);
|
|
if ((dwAttribs == INVALID_FILE_ATTRIBUTES) ||
|
|
(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), TEXT("en-US"));
|
|
|
|
StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath),
|
|
TEXT("%s\\%s.ini"), lpResPath, szBuffer);
|
|
}
|
|
|
|
/* Verify that the file exists, otherwise fall back to internal (localized) resource */
|
|
dwAttribs = GetFileAttributes(szIniPath);
|
|
if ((dwAttribs == INVALID_FILE_ATTRIBUTES) ||
|
|
(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
return FALSE; // For localized resources, see the general function.
|
|
}
|
|
|
|
/* Try to load the default localized strings */
|
|
GetPrivateProfileString(TEXT("Defaults"), TEXT("AppTitle"), TEXT("ReactOS - Welcome") /* default */,
|
|
szAppTitle, ARRAYSIZE(szAppTitle), szIniPath);
|
|
GetPrivateProfileString(TEXT("Defaults"), TEXT("DefaultTopicTitle"), TEXT("") /* default */,
|
|
szDefaultTitle, ARRAYSIZE(szDefaultTitle), szIniPath);
|
|
if (!GetPrivateProfileString(TEXT("Defaults"), TEXT("DefaultTopicDescription"), TEXT("") /* default */,
|
|
szDefaultDesc, ARRAYSIZE(szDefaultDesc), szIniPath))
|
|
{
|
|
*szDefaultDesc = 0;
|
|
}
|
|
else
|
|
{
|
|
TranslateEscapes(szDefaultDesc);
|
|
}
|
|
|
|
/* Allocate a buffer big enough to hold all the section names */
|
|
for (dwSize = BUFFER_SIZE; ; dwSize += BUFFER_SIZE)
|
|
{
|
|
lpszSections = HeapAlloc(GetProcessHeap(), 0, dwSize * sizeof(TCHAR));
|
|
if (!lpszSections)
|
|
return TRUE; // FIXME!
|
|
dwRet = GetPrivateProfileSectionNames(lpszSections, dwSize, szIniPath);
|
|
if (dwRet < dwSize - 2)
|
|
break;
|
|
HeapFree(GetProcessHeap(), 0, lpszSections);
|
|
}
|
|
|
|
/* Loop over the sections and load the topics */
|
|
lpszSection = lpszSections;
|
|
for (; lpszSection && *lpszSection; lpszSection += (_tcslen(lpszSection) + 1))
|
|
{
|
|
/* Ignore everything that is not a topic */
|
|
if (_tcsnicmp(lpszSection, TEXT("Topic"), 5) != 0)
|
|
continue;
|
|
|
|
lpszCommand = NULL, lpszAction = NULL;
|
|
|
|
/* Retrieve the information */
|
|
GetPrivateProfileString(lpszSection, TEXT("MenuText"), TEXT("") /* default */,
|
|
newTopic.szText, ARRAYSIZE(newTopic.szText), szIniPath);
|
|
GetPrivateProfileString(lpszSection, TEXT("Title"), TEXT("") /* default */,
|
|
newTopic.szTitle, ARRAYSIZE(newTopic.szTitle), szIniPath);
|
|
GetPrivateProfileString(lpszSection, TEXT("Description"), TEXT("") /* default */,
|
|
newTopic.szDesc, ARRAYSIZE(newTopic.szDesc), szIniPath);
|
|
|
|
GetPrivateProfileString(lpszSection, TEXT("ConfigCommand"), TEXT("") /* default */,
|
|
newTopic.szCommand, ARRAYSIZE(newTopic.szCommand), szIniPath);
|
|
|
|
/* Only care about command arguments if we actually have a command */
|
|
if (*newTopic.szCommand)
|
|
{
|
|
lpszCommand = newTopic.szCommand;
|
|
GetPrivateProfileString(lpszSection, TEXT("ConfigArgs"), TEXT("") /* default */,
|
|
newTopic.szArgs, ARRAYSIZE(newTopic.szArgs), szIniPath);
|
|
}
|
|
/* Only care about custom actions if we actually don't have a command */
|
|
else // if (!*newTopic.szCommand)
|
|
{
|
|
lpszAction = newTopic.szCommand;
|
|
GetPrivateProfileString(lpszSection, TEXT("Action"), TEXT("") /* default */,
|
|
newTopic.szCommand, ARRAYSIZE(newTopic.szCommand), szIniPath);
|
|
}
|
|
|
|
/* Allocate a new topic */
|
|
pTopic = AddNewTopicEx(newTopic.szText,
|
|
newTopic.szTitle,
|
|
newTopic.szDesc,
|
|
lpszCommand,
|
|
newTopic.szArgs,
|
|
lpszAction);
|
|
if (!pTopic)
|
|
break; // Cannot reallocate more
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, lpszSections);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static VOID
|
|
LoadConfiguration(VOID)
|
|
{
|
|
DWORD dwAttribs;
|
|
BOOL bLoadDefaultResources;
|
|
TCHAR szAppPath[MAX_PATH];
|
|
TCHAR szIniPath[MAX_PATH];
|
|
TCHAR szResPath[MAX_PATH];
|
|
|
|
/* Initialize the topic list */
|
|
InitializeTopicList();
|
|
|
|
/*
|
|
* First, try to load the default internal (localized) strings.
|
|
* They can be redefined by the localized INI files.
|
|
*/
|
|
if (!LoadString(hInstance, IDS_APPTITLE, szAppTitle, ARRAYSIZE(szAppTitle)))
|
|
StringCchCopy(szAppTitle, ARRAYSIZE(szAppTitle), TEXT("ReactOS - Welcome"));
|
|
if (!LoadString(hInstance, IDS_DEFAULT_TOPIC_TITLE, szDefaultTitle, ARRAYSIZE(szDefaultTitle)))
|
|
*szDefaultTitle = 0;
|
|
if (!LoadString(hInstance, IDS_DEFAULT_TOPIC_DESC, szDefaultDesc, ARRAYSIZE(szDefaultDesc)))
|
|
*szDefaultDesc = 0;
|
|
|
|
/* Retrieve the full path to this application */
|
|
GetModuleFileName(NULL, szAppPath, ARRAYSIZE(szAppPath));
|
|
if (*szAppPath)
|
|
{
|
|
LPTSTR lpFileName = _tcsrchr(szAppPath, _T('\\'));
|
|
if (lpFileName)
|
|
*lpFileName = 0;
|
|
else
|
|
*szAppPath = 0;
|
|
}
|
|
|
|
/* Build the full INI file path name */
|
|
StringCchPrintf(szIniPath, ARRAYSIZE(szIniPath), TEXT("%s\\welcome.ini"), szAppPath);
|
|
|
|
/* Verify that the file exists, otherwise use the default configuration */
|
|
dwAttribs = GetFileAttributes(szIniPath);
|
|
if ((dwAttribs == INVALID_FILE_ATTRIBUTES) ||
|
|
(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
/* Use the default internal (localized) resources */
|
|
LoadLocalizedResourcesInternal();
|
|
return;
|
|
}
|
|
|
|
/* Load the settings from the INI configuration file */
|
|
bDisplayCheckBox = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("DisplayCheckBox"), FALSE /* default */, szIniPath);
|
|
bDisplayExitBtn = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("DisplayExitButton"), TRUE /* default */, szIniPath);
|
|
|
|
/* Load the default internal (localized) resources if needed */
|
|
bLoadDefaultResources = !!GetPrivateProfileInt(TEXT("Welcome"), TEXT("LoadDefaultResources"), FALSE /* default */, szIniPath);
|
|
if (bLoadDefaultResources)
|
|
LoadLocalizedResourcesInternal();
|
|
|
|
GetPrivateProfileString(TEXT("Welcome"), TEXT("ResourceDir"), TEXT("") /* default */,
|
|
szResPath, ARRAYSIZE(szResPath), szIniPath);
|
|
|
|
/* Set the current directory to the one of this application, and retrieve the resources */
|
|
SetCurrentDirectory(szAppPath);
|
|
if (!LoadLocalizedResourcesFromINI(LOCALE_USER_DEFAULT, szResPath))
|
|
{
|
|
/*
|
|
* Loading localized resources from INI file failed, try to load the
|
|
* internal resources only if they were not already loaded earlier.
|
|
*/
|
|
if (!bLoadDefaultResources)
|
|
LoadLocalizedResourcesInternal();
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
FreeResources(VOID)
|
|
{
|
|
if (!pTopics)
|
|
return;
|
|
|
|
while (dwNumberTopics--)
|
|
{
|
|
if (pTopics[dwNumberTopics])
|
|
HeapFree(GetProcessHeap(), 0, pTopics[dwNumberTopics]);
|
|
}
|
|
HeapFree(GetProcessHeap(), 0, pTopics);
|
|
pTopics = NULL;
|
|
dwNumberTopics = 0;
|
|
}
|
|
|
|
#if 0
|
|
static VOID
|
|
ShowLastWin32Error(HWND hWnd)
|
|
{
|
|
LPTSTR lpMessageBuffer = NULL;
|
|
DWORD dwError = GetLastError();
|
|
|
|
if (dwError == ERROR_SUCCESS)
|
|
return;
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
dwError,
|
|
LANG_USER_DEFAULT,
|
|
(LPTSTR)&lpMessageBuffer,
|
|
0, NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
MessageBox(hWnd, lpMessageBuffer, szAppTitle, MB_OK | MB_ICONERROR);
|
|
LocalFree(lpMessageBuffer);
|
|
}
|
|
#endif
|
|
|
|
int WINAPI
|
|
_tWinMain(HINSTANCE hInst,
|
|
HINSTANCE hPrevInstance,
|
|
LPTSTR lpszCmdLine,
|
|
int nCmdShow)
|
|
{
|
|
HANDLE hMutex = NULL;
|
|
WNDCLASSEX wndclass;
|
|
MSG msg;
|
|
HWND hWndFocus;
|
|
INT xPos, yPos;
|
|
INT xWidth, yHeight;
|
|
RECT rcWindow;
|
|
HICON hMainIcon;
|
|
HMENU hSystemMenu;
|
|
DWORD dwStyle = WS_OVERLAPPED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
|
|
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
|
|
|
BITMAP BitmapInfo;
|
|
ULONG ulInnerWidth = TITLE_WIDTH;
|
|
ULONG ulInnerHeight = (TITLE_WIDTH * 3) / 4;
|
|
ULONG ulTitleHeight = TITLE_HEIGHT + 3;
|
|
|
|
UNREFERENCED_PARAMETER(hPrevInstance);
|
|
UNREFERENCED_PARAMETER(lpszCmdLine);
|
|
|
|
/* Ensure only one instance is running */
|
|
hMutex = CreateMutex(NULL, FALSE, szWindowClass);
|
|
if (hMutex && (GetLastError() == ERROR_ALREADY_EXISTS))
|
|
{
|
|
/* If already started, find its window */
|
|
hWndMain = FindWindow(szWindowClass, NULL);
|
|
|
|
/* Activate window */
|
|
ShowWindow(hWndMain, SW_SHOWNORMAL);
|
|
SetForegroundWindow(hWndMain);
|
|
|
|
/* Close the mutex handle and quit */
|
|
CloseHandle(hMutex);
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
/* Mirroring is enabled from within the resources */
|
|
switch (GetUserDefaultUILanguage())
|
|
{
|
|
case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
|
|
SetProcessDefaultLayout(LAYOUT_RTL);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
hInstance = hInst;
|
|
|
|
/* Load icons */
|
|
hMainIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN));
|
|
|
|
/* Register the window class */
|
|
wndclass.cbSize = sizeof(wndclass);
|
|
wndclass.style = CS_HREDRAW | CS_VREDRAW;
|
|
wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
|
|
wndclass.cbClsExtra = 0;
|
|
wndclass.cbWndExtra = 0;
|
|
wndclass.hInstance = hInstance;
|
|
wndclass.hIcon = hMainIcon;
|
|
wndclass.hIconSm = NULL;
|
|
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wndclass.hbrBackground = NULL;
|
|
wndclass.lpszMenuName = NULL;
|
|
wndclass.lpszClassName = szWindowClass;
|
|
|
|
RegisterClassEx(&wndclass);
|
|
|
|
/* Load the banner bitmap, and compute the window dimensions */
|
|
hTitleBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TITLE_BITMAP));
|
|
if (hTitleBitmap)
|
|
{
|
|
GetObject(hTitleBitmap, sizeof(BitmapInfo), &BitmapInfo);
|
|
ulInnerWidth = BitmapInfo.bmWidth;
|
|
ulInnerHeight = (ulInnerWidth * 3) / 4;
|
|
ulTitleHeight = BitmapInfo.bmHeight + 3;
|
|
DeleteObject(hTitleBitmap);
|
|
}
|
|
ulInnerHeight -= GetSystemMetrics(SM_CYCAPTION);
|
|
|
|
rcWindow.top = 0;
|
|
rcWindow.bottom = ulInnerHeight - 1;
|
|
rcWindow.left = 0;
|
|
rcWindow.right = ulInnerWidth - 1;
|
|
|
|
AdjustWindowRect(&rcWindow, dwStyle, FALSE);
|
|
xWidth = rcWindow.right - rcWindow.left;
|
|
yHeight = rcWindow.bottom - rcWindow.top;
|
|
|
|
/* Compute the window position */
|
|
xPos = (GetSystemMetrics(SM_CXSCREEN) - xWidth) / 2;
|
|
yPos = (GetSystemMetrics(SM_CYSCREEN) - yHeight) / 2;
|
|
|
|
rcTitlePanel.top = 0;
|
|
rcTitlePanel.bottom = ulTitleHeight;
|
|
rcTitlePanel.left = 0;
|
|
rcTitlePanel.right = ulInnerWidth - 1;
|
|
|
|
rcLeftPanel.top = rcTitlePanel.bottom;
|
|
rcLeftPanel.bottom = ulInnerHeight - 1;
|
|
rcLeftPanel.left = 0;
|
|
rcLeftPanel.right = ulInnerWidth / 3;
|
|
|
|
rcRightPanel.top = rcLeftPanel.top;
|
|
rcRightPanel.bottom = rcLeftPanel.bottom;
|
|
rcRightPanel.left = rcLeftPanel.right;
|
|
rcRightPanel.right = ulInnerWidth - 1;
|
|
|
|
/* Load the configuration and the resources */
|
|
LoadConfiguration();
|
|
|
|
/* Create main window */
|
|
hWndMain = CreateWindow(szWindowClass,
|
|
szAppTitle,
|
|
dwStyle,
|
|
xPos,
|
|
yPos,
|
|
xWidth,
|
|
yHeight,
|
|
0,
|
|
0,
|
|
hInstance,
|
|
NULL);
|
|
|
|
hSystemMenu = GetSystemMenu(hWndMain, FALSE);
|
|
if (hSystemMenu)
|
|
{
|
|
RemoveMenu(hSystemMenu, SC_SIZE, MF_BYCOMMAND);
|
|
RemoveMenu(hSystemMenu, SC_MAXIMIZE, MF_BYCOMMAND);
|
|
}
|
|
|
|
ShowWindow(hWndMain, nCmdShow);
|
|
UpdateWindow(hWndMain);
|
|
|
|
while (GetMessage(&msg, NULL, 0, 0) != FALSE)
|
|
{
|
|
/* Check for ENTER key presses */
|
|
if (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN)
|
|
{
|
|
/*
|
|
* The user pressed the ENTER key. Retrieve the handle to the
|
|
* child window that has the keyboard focus, and send it a
|
|
* WM_COMMAND message.
|
|
*/
|
|
hWndFocus = GetFocus();
|
|
if (hWndFocus)
|
|
{
|
|
SendMessage(hWndMain, WM_COMMAND,
|
|
(WPARAM)GetDlgCtrlID(hWndFocus), (LPARAM)hWndFocus);
|
|
}
|
|
}
|
|
/* Allow using keyboard navigation */
|
|
else if (!IsDialogMessage(hWndMain, &msg))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
/* Cleanup */
|
|
FreeResources();
|
|
|
|
/* Close the mutex handle and quit */
|
|
CloseHandle(hMutex);
|
|
return msg.wParam;
|
|
}
|
|
|
|
|
|
INT_PTR CALLBACK
|
|
ButtonSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static WPARAM wParamOld = 0;
|
|
static LPARAM lParamOld = 0;
|
|
|
|
LONG i;
|
|
|
|
if (uMsg == WM_MOUSEMOVE)
|
|
{
|
|
/* Ignore mouse-move messages on the same point */
|
|
if ((wParam == wParamOld) && (lParam == lParamOld))
|
|
return 0;
|
|
|
|
/* Retrieve the topic index of this button */
|
|
i = GetWindowLongPtr(hWnd, GWLP_ID) - TOPIC_BTN_ID_BASE;
|
|
|
|
/*
|
|
* Change the focus to this button if the current topic index differs
|
|
* (we will receive WM_SETFOCUS afterwards).
|
|
*/
|
|
if (nTopic != i)
|
|
SetFocus(hWnd);
|
|
|
|
wParamOld = wParam;
|
|
lParamOld = lParam;
|
|
}
|
|
else if (uMsg == WM_SETFOCUS)
|
|
{
|
|
/* Retrieve the topic index of this button */
|
|
i = GetWindowLongPtr(hWnd, GWLP_ID) - TOPIC_BTN_ID_BASE;
|
|
|
|
/* Change the current topic index and repaint the description panel */
|
|
if (nTopic != i)
|
|
{
|
|
nTopic = i;
|
|
InvalidateRect(hWndMain, &rcRightPanel, TRUE);
|
|
}
|
|
}
|
|
else if (uMsg == WM_KILLFOCUS)
|
|
{
|
|
/*
|
|
* We lost focus, either because the user changed button focus,
|
|
* or because the main window to which we belong went inactivated.
|
|
* If we are in the latter case, we ignore the focus change.
|
|
* If we are in the former case, we reset to the default topic.
|
|
*/
|
|
if (GetParent(hWnd) == GetForegroundWindow())
|
|
{
|
|
nTopic = -1;
|
|
InvalidateRect(hWndMain, &rcRightPanel, TRUE);
|
|
}
|
|
}
|
|
|
|
return CallWindowProc(fnOldBtn, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
|
|
static BOOL
|
|
RunAction(INT nTopic)
|
|
{
|
|
PCWSTR Command = NULL, Args = NULL;
|
|
|
|
if (nTopic < 0)
|
|
return TRUE;
|
|
|
|
Command = pTopics[nTopic]->szCommand;
|
|
if (/* !Command && */ !*Command)
|
|
return TRUE;
|
|
|
|
/* Check for known actions */
|
|
if (!pTopics[nTopic]->bIsCommand)
|
|
{
|
|
if (!_tcsicmp(Command, TEXT("<exit>")))
|
|
return FALSE;
|
|
|
|
if (!_tcsnicmp(Command, TEXT("<msg>"), 5))
|
|
{
|
|
MessageBox(hWndMain, Command + 5, TEXT("ReactOS"), MB_OK | MB_TASKMODAL);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
/* Run the command */
|
|
{
|
|
Args = pTopics[nTopic]->szArgs;
|
|
if (!*Args) Args = NULL;
|
|
ShellExecute(NULL, NULL,
|
|
Command, Args,
|
|
NULL, SW_SHOWDEFAULT);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static DWORD
|
|
GetButtonHeight(HDC hDC,
|
|
HFONT hFont,
|
|
LPCTSTR szText,
|
|
DWORD dwWidth)
|
|
{
|
|
HFONT hOldFont;
|
|
RECT rect;
|
|
|
|
rect.left = 0;
|
|
rect.right = dwWidth - 20;
|
|
rect.top = 0;
|
|
rect.bottom = 25;
|
|
|
|
hOldFont = (HFONT)SelectObject(hDC, hFont);
|
|
DrawText(hDC, szText, -1, &rect, DT_TOP | DT_CALCRECT | DT_WORDBREAK);
|
|
SelectObject(hDC, hOldFont);
|
|
|
|
return (rect.bottom-rect.top + 14);
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
UINT i;
|
|
INT nLength;
|
|
HDC ScreenDC;
|
|
LOGFONT lf;
|
|
DWORD dwTop;
|
|
DWORD dwHeight = 0;
|
|
TCHAR szText[80];
|
|
|
|
UNREFERENCED_PARAMETER(wParam);
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
hbrLightBlue = CreateSolidBrush(LIGHT_BLUE);
|
|
hbrDarkBlue = CreateSolidBrush(DARK_BLUE);
|
|
|
|
ZeroMemory(&lf, sizeof(lf));
|
|
|
|
lf.lfEscapement = 0;
|
|
lf.lfOrientation = 0; // TA_BASELINE;
|
|
// lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
|
|
lf.lfCharSet = ANSI_CHARSET;
|
|
lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
|
|
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
|
lf.lfQuality = DEFAULT_QUALITY;
|
|
lf.lfPitchAndFamily = FF_DONTCARE;
|
|
if (LoadString(hInstance, IDS_FONTNAME, lf.lfFaceName, ARRAYSIZE(lf.lfFaceName)) == 0)
|
|
StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), TEXT("Tahoma"));
|
|
|
|
/* Topic title font */
|
|
lf.lfHeight = -18;
|
|
lf.lfWidth = 0;
|
|
lf.lfWeight = FW_NORMAL;
|
|
hFontTopicTitle = CreateFontIndirect(&lf);
|
|
|
|
/* Topic description font */
|
|
lf.lfHeight = -11;
|
|
lf.lfWidth = 0;
|
|
lf.lfWeight = FW_THIN;
|
|
hFontTopicDescription = CreateFontIndirect(&lf);
|
|
|
|
/* Topic button font */
|
|
lf.lfHeight = -11;
|
|
lf.lfWidth = 0;
|
|
lf.lfWeight = FW_BOLD;
|
|
hFontTopicButton = CreateFontIndirect(&lf);
|
|
|
|
/* Load title bitmap */
|
|
if (hTitleBitmap)
|
|
hTitleBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TITLE_BITMAP));
|
|
|
|
/* Load topic bitmaps */
|
|
hDefaultTopicBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_DEFAULT_TOPIC_BITMAP));
|
|
for (i = 0; i < dwNumberTopics; i++)
|
|
{
|
|
// FIXME: Not implemented yet!
|
|
// pTopics[i]->hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_TOPIC_BITMAP0 + i));
|
|
pTopics[i]->hBitmap = NULL;
|
|
}
|
|
|
|
ScreenDC = GetWindowDC(hWnd);
|
|
hdcMem = CreateCompatibleDC(ScreenDC);
|
|
ReleaseDC(hWnd, ScreenDC);
|
|
|
|
/* Load and create the menu buttons */
|
|
dwTop = rcLeftPanel.top;
|
|
for (i = 0; i < dwNumberTopics; i++)
|
|
{
|
|
if (*pTopics[i]->szText)
|
|
{
|
|
dwHeight = GetButtonHeight(hdcMem,
|
|
hFontTopicButton,
|
|
pTopics[i]->szText,
|
|
rcLeftPanel.right - rcLeftPanel.left);
|
|
|
|
pTopics[i]->hWndButton = CreateWindow(TEXT("BUTTON"),
|
|
pTopics[i]->szText,
|
|
(*pTopics[i]->szCommand ? 0 : WS_DISABLED) |
|
|
WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP |
|
|
BS_MULTILINE | BS_OWNERDRAW,
|
|
rcLeftPanel.left,
|
|
dwTop,
|
|
rcLeftPanel.right - rcLeftPanel.left,
|
|
dwHeight,
|
|
hWnd,
|
|
(HMENU)IntToPtr(TOPIC_BTN_ID_BASE + i), // Similar to SetWindowLongPtr(GWLP_ID)
|
|
hInstance,
|
|
NULL);
|
|
nDefaultTopic = i;
|
|
SendMessage(pTopics[i]->hWndButton, WM_SETFONT, (WPARAM)hFontTopicButton, MAKELPARAM(TRUE, 0));
|
|
fnOldBtn = (WNDPROC)SetWindowLongPtr(pTopics[i]->hWndButton, GWLP_WNDPROC, (DWORD_PTR)ButtonSubclassWndProc);
|
|
}
|
|
else
|
|
{
|
|
pTopics[i]->hWndButton = NULL;
|
|
}
|
|
|
|
dwTop += dwHeight;
|
|
}
|
|
|
|
/* Create the checkbox */
|
|
if (bDisplayCheckBox)
|
|
{
|
|
nLength = LoadString(hInstance, IDS_CHECKTEXT, szText, ARRAYSIZE(szText));
|
|
if (nLength > 0)
|
|
{
|
|
lf.lfHeight = -10;
|
|
lf.lfWidth = 0;
|
|
lf.lfWeight = FW_THIN;
|
|
hFontCheckButton = CreateFontIndirect(&lf);
|
|
|
|
hWndCheckButton = CreateWindow(TEXT("BUTTON"),
|
|
szText,
|
|
WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP |
|
|
BS_AUTOCHECKBOX | BS_MULTILINE /**/| BS_FLAT/**/,
|
|
rcLeftPanel.left + 8,
|
|
rcLeftPanel.bottom - 8 - 13,
|
|
rcLeftPanel.right - rcLeftPanel.left - 16,
|
|
13,
|
|
hWnd,
|
|
(HMENU)IDC_CHECKBUTTON,
|
|
hInstance,
|
|
NULL);
|
|
SendMessage(hWndCheckButton, WM_SETFONT, (WPARAM)hFontCheckButton, MAKELPARAM(TRUE, 0));
|
|
}
|
|
else
|
|
{
|
|
hFontCheckButton = NULL;
|
|
hWndCheckButton = NULL;
|
|
}
|
|
}
|
|
|
|
/* Create the "Exit" button */
|
|
if (bDisplayExitBtn)
|
|
{
|
|
nLength = LoadString(hInstance, IDS_CLOSETEXT, szText, ARRAYSIZE(szText));
|
|
if (nLength > 0)
|
|
{
|
|
hWndCloseButton = CreateWindow(TEXT("BUTTON"),
|
|
szText,
|
|
WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_FLAT,
|
|
rcRightPanel.right - 8 - 57,
|
|
rcRightPanel.bottom - 8 - 21,
|
|
57,
|
|
21,
|
|
hWnd,
|
|
(HMENU)IDC_CLOSEBUTTON,
|
|
hInstance,
|
|
NULL);
|
|
nDefaultTopic = -1;
|
|
SendMessage(hWndCloseButton, WM_SETFONT, (WPARAM)hFontTopicButton, MAKELPARAM(TRUE, 0));
|
|
}
|
|
else
|
|
{
|
|
hWndCloseButton = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
/* Retrieve the low-word from wParam */
|
|
wParam = LOWORD(wParam);
|
|
|
|
/* Execute action */
|
|
if (wParam == IDC_CLOSEBUTTON)
|
|
{
|
|
DestroyWindow(hWnd);
|
|
}
|
|
else if (wParam - TOPIC_BTN_ID_BASE < dwNumberTopics)
|
|
{
|
|
if (RunAction(wParam - TOPIC_BTN_ID_BASE) == FALSE)
|
|
DestroyWindow(hWnd); // Corresponds to a <exit> action.
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static VOID
|
|
PaintBanner(HDC hdc, LPRECT rcPanel)
|
|
{
|
|
HBITMAP hOldBitmap;
|
|
HBRUSH hOldBrush;
|
|
|
|
/* Title bitmap */
|
|
hOldBitmap = (HBITMAP)SelectObject(hdcMem, hTitleBitmap);
|
|
BitBlt(hdc,
|
|
rcPanel->left,
|
|
rcPanel->top,
|
|
rcPanel->right - rcPanel->left,
|
|
rcPanel->bottom - 3,
|
|
hdcMem, 0, 0, SRCCOPY);
|
|
SelectObject(hdcMem, hOldBitmap);
|
|
|
|
/* Dark blue line */
|
|
hOldBrush = (HBRUSH)SelectObject(hdc, hbrDarkBlue);
|
|
PatBlt(hdc,
|
|
rcPanel->left,
|
|
rcPanel->bottom - 3,
|
|
rcPanel->right - rcPanel->left,
|
|
3,
|
|
PATCOPY);
|
|
SelectObject(hdc, hOldBrush);
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HPEN hPen;
|
|
HPEN hOldPen;
|
|
HDC hdc;
|
|
PAINTSTRUCT ps;
|
|
HBITMAP hOldBitmap = NULL;
|
|
HBRUSH hOldBrush;
|
|
HFONT hOldFont;
|
|
RECT rcTitle, rcDescription;
|
|
BITMAP bmpInfo;
|
|
TCHAR szVersion[50];
|
|
LPTSTR lpTitle = NULL, lpDesc = NULL;
|
|
|
|
UNREFERENCED_PARAMETER(wParam);
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
hdc = BeginPaint(hWnd, &ps);
|
|
|
|
/* Banner panel */
|
|
PaintBanner(hdc, &rcTitlePanel);
|
|
|
|
/* Left panel */
|
|
hOldBrush = (HBRUSH)SelectObject(hdc, hbrLightBlue);
|
|
PatBlt(hdc,
|
|
rcLeftPanel.left,
|
|
rcLeftPanel.top,
|
|
rcLeftPanel.right - rcLeftPanel.left,
|
|
rcLeftPanel.bottom - rcLeftPanel.top,
|
|
PATCOPY);
|
|
SelectObject(hdc, hOldBrush);
|
|
|
|
/* Right panel */
|
|
hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(WHITE_BRUSH));
|
|
PatBlt(hdc,
|
|
rcRightPanel.left,
|
|
rcRightPanel.top,
|
|
rcRightPanel.right - rcRightPanel.left,
|
|
rcRightPanel.bottom - rcRightPanel.top,
|
|
PATCOPY);
|
|
SelectObject(hdc, hOldBrush);
|
|
|
|
/* Draw dark vertical line */
|
|
hPen = CreatePen(PS_SOLID, 0, DARK_BLUE);
|
|
hOldPen = (HPEN)SelectObject(hdc, hPen);
|
|
MoveToEx(hdc, rcRightPanel.left, rcRightPanel.top, NULL);
|
|
LineTo(hdc, rcRightPanel.left, rcRightPanel.bottom);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
/* Draw topic bitmap */
|
|
if ((nTopic == -1) && (hDefaultTopicBitmap))
|
|
{
|
|
GetObject(hDefaultTopicBitmap, sizeof(bmpInfo), &bmpInfo);
|
|
hOldBitmap = (HBITMAP)SelectObject(hdcMem, hDefaultTopicBitmap);
|
|
BitBlt(hdc,
|
|
rcRightPanel.right - bmpInfo.bmWidth,
|
|
rcRightPanel.bottom - bmpInfo.bmHeight,
|
|
bmpInfo.bmWidth,
|
|
bmpInfo.bmHeight,
|
|
hdcMem,
|
|
0,
|
|
0,
|
|
SRCCOPY);
|
|
}
|
|
else if ((nTopic != -1) && (pTopics[nTopic]->hBitmap))
|
|
{
|
|
GetObject(pTopics[nTopic]->hBitmap, sizeof(bmpInfo), &bmpInfo);
|
|
hOldBitmap = (HBITMAP)SelectObject(hdcMem, pTopics[nTopic]->hBitmap);
|
|
BitBlt(hdc,
|
|
rcRightPanel.right - bmpInfo.bmWidth,
|
|
rcRightPanel.bottom - bmpInfo.bmHeight,
|
|
bmpInfo.bmWidth,
|
|
bmpInfo.bmHeight,
|
|
hdcMem,
|
|
0,
|
|
0,
|
|
SRCCOPY);
|
|
}
|
|
|
|
if (nTopic == -1)
|
|
{
|
|
lpTitle = szDefaultTitle;
|
|
lpDesc = szDefaultDesc;
|
|
}
|
|
else
|
|
{
|
|
lpTitle = pTopics[nTopic]->szTitle;
|
|
lpDesc = pTopics[nTopic]->szDesc;
|
|
}
|
|
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
|
|
/* Draw version information */
|
|
StringCchCopy(szVersion, ARRAYSIZE(szVersion),
|
|
TEXT("ReactOS ") TEXT(KERNEL_VERSION_STR));
|
|
|
|
/*
|
|
* Compute the original rect (position & size) of the version info,
|
|
* depending whether the checkbox is displayed (version info in the
|
|
* right panel) or not (version info in the left panel).
|
|
*/
|
|
if (bDisplayCheckBox)
|
|
rcTitle = rcRightPanel;
|
|
else
|
|
rcTitle = rcLeftPanel;
|
|
|
|
rcTitle.left = rcTitle.left + 8;
|
|
rcTitle.right = rcTitle.right - 5;
|
|
rcTitle.top = rcTitle.bottom - 43;
|
|
rcTitle.bottom = rcTitle.bottom - 8;
|
|
|
|
hOldFont = (HFONT)SelectObject(hdc, hFontTopicDescription);
|
|
DrawText(hdc, szVersion, -1, &rcTitle, DT_BOTTOM | DT_CALCRECT | DT_SINGLELINE);
|
|
SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
|
|
DrawText(hdc, szVersion, -1, &rcTitle, DT_BOTTOM | DT_SINGLELINE);
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
/* Draw topic title */
|
|
rcTitle.left = rcRightPanel.left + 12;
|
|
rcTitle.right = rcRightPanel.right - 8;
|
|
rcTitle.top = rcRightPanel.top + 8;
|
|
rcTitle.bottom = rcTitle.top + 57;
|
|
hOldFont = (HFONT)SelectObject(hdc, hFontTopicTitle);
|
|
DrawText(hdc, lpTitle, -1, &rcTitle, DT_TOP | DT_CALCRECT);
|
|
SetTextColor(hdc, DARK_BLUE);
|
|
DrawText(hdc, lpTitle, -1, &rcTitle, DT_TOP);
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
/* Draw topic description */
|
|
rcDescription.left = rcRightPanel.left + 12;
|
|
rcDescription.right = rcRightPanel.right - 8;
|
|
rcDescription.top = rcTitle.bottom + 8;
|
|
rcDescription.bottom = rcRightPanel.bottom - 20;
|
|
hOldFont = (HFONT)SelectObject(hdc, hFontTopicDescription);
|
|
SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
|
|
DrawText(hdc, lpDesc, -1, &rcDescription, DT_TOP | DT_WORDBREAK);
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
SetBkMode(hdc, OPAQUE);
|
|
|
|
SelectObject(hdcMem, hOldBrush);
|
|
SelectObject(hdcMem, hOldBitmap);
|
|
|
|
EndPaint(hWnd, &ps);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam;
|
|
HPEN hPen, hOldPen;
|
|
HBRUSH hOldBrush;
|
|
INT iBkMode;
|
|
TCHAR szText[80];
|
|
|
|
UNREFERENCED_PARAMETER(hWnd);
|
|
UNREFERENCED_PARAMETER(wParam);
|
|
|
|
#if 0
|
|
/* Neither the checkbox button nor the close button implement owner-drawing */
|
|
if (lpDis->hwndItem == hWndCheckButton)
|
|
return 0;
|
|
if (lpDis->hwndItem == hWndCloseButton)
|
|
{
|
|
DrawFrameControl(lpDis->hDC,
|
|
&lpDis->rcItem,
|
|
DFC_BUTTON,
|
|
DFCS_BUTTONPUSH | DFCS_FLAT);
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (lpDis->CtlID == (ULONG)(TOPIC_BTN_ID_BASE + nTopic))
|
|
hOldBrush = (HBRUSH)SelectObject(lpDis->hDC, GetStockObject(WHITE_BRUSH));
|
|
else
|
|
hOldBrush = (HBRUSH)SelectObject(lpDis->hDC, hbrLightBlue);
|
|
|
|
PatBlt(lpDis->hDC,
|
|
lpDis->rcItem.left,
|
|
lpDis->rcItem.top,
|
|
lpDis->rcItem.right,
|
|
lpDis->rcItem.bottom,
|
|
PATCOPY);
|
|
SelectObject(lpDis->hDC, hOldBrush);
|
|
|
|
hPen = CreatePen(PS_SOLID, 0, DARK_BLUE);
|
|
hOldPen = (HPEN)SelectObject(lpDis->hDC, hPen);
|
|
MoveToEx(lpDis->hDC, lpDis->rcItem.left, lpDis->rcItem.bottom - 1, NULL);
|
|
LineTo(lpDis->hDC, lpDis->rcItem.right, lpDis->rcItem.bottom - 1);
|
|
SelectObject(lpDis->hDC, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
InflateRect(&lpDis->rcItem, -10, -4);
|
|
OffsetRect(&lpDis->rcItem, 0, 1);
|
|
GetWindowText(lpDis->hwndItem, szText, ARRAYSIZE(szText));
|
|
SetTextColor(lpDis->hDC, GetSysColor(IsWindowEnabled(lpDis->hwndItem) ?
|
|
COLOR_WINDOWTEXT : COLOR_GRAYTEXT));
|
|
iBkMode = SetBkMode(lpDis->hDC, TRANSPARENT);
|
|
DrawText(lpDis->hDC, szText, -1, &lpDis->rcItem, DT_TOP | DT_LEFT | DT_WORDBREAK);
|
|
SetBkMode(lpDis->hDC, iBkMode);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static WPARAM wParamOld = 0;
|
|
static LPARAM lParamOld = 0;
|
|
|
|
/* Ignore mouse-move messages on the same point */
|
|
if ((wParam == wParamOld) && (lParam == lParamOld))
|
|
return 0;
|
|
|
|
/*
|
|
* If the user moves the mouse over the main window, outside of the
|
|
* topic buttons, reset the current topic to the default one and
|
|
* change the focus to some other default button (to keep keyboard
|
|
* navigation possible).
|
|
*/
|
|
if (nTopic != -1)
|
|
{
|
|
INT nOldTopic = nTopic;
|
|
nTopic = -1;
|
|
/* Also repaint the buttons, otherwise nothing repaints... */
|
|
InvalidateRect(pTopics[nOldTopic]->hWndButton, NULL, TRUE);
|
|
|
|
/* Set the focus to some other default button */
|
|
if (hWndCheckButton)
|
|
SetFocus(hWndCheckButton);
|
|
else if (hWndCloseButton)
|
|
SetFocus(hWndCloseButton);
|
|
// SetFocus(hWnd);
|
|
|
|
/* Repaint the description panel */
|
|
InvalidateRect(hWndMain, &rcRightPanel, TRUE);
|
|
}
|
|
|
|
wParamOld = wParam;
|
|
lParamOld = lParam;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnCtlColorStatic(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
UNREFERENCED_PARAMETER(hWnd);
|
|
|
|
if ((HWND)lParam == hWndCheckButton)
|
|
{
|
|
SetBkMode((HDC)wParam, TRANSPARENT);
|
|
return (LRESULT)hbrLightBlue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnActivate(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
UNREFERENCED_PARAMETER(hWnd);
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
if (wParam != WA_INACTIVE)
|
|
{
|
|
/*
|
|
* The main window is re-activated, set the focus back to
|
|
* either the current topic or a default button.
|
|
*/
|
|
if (nTopic != -1)
|
|
SetFocus(pTopics[nTopic]->hWndButton);
|
|
else if (hWndCheckButton)
|
|
SetFocus(hWndCheckButton);
|
|
else if (hWndCloseButton)
|
|
SetFocus(hWndCloseButton);
|
|
|
|
// InvalidateRect(hWndMain, &rcRightPanel, TRUE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static LRESULT
|
|
OnDestroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
UINT i;
|
|
|
|
UNREFERENCED_PARAMETER(hWnd);
|
|
UNREFERENCED_PARAMETER(wParam);
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
for (i = 0; i < dwNumberTopics; i++)
|
|
{
|
|
if (pTopics[i]->hWndButton)
|
|
DestroyWindow(pTopics[i]->hWndButton);
|
|
}
|
|
|
|
if (hWndCloseButton)
|
|
DestroyWindow(hWndCloseButton);
|
|
|
|
if (hWndCheckButton)
|
|
DestroyWindow(hWndCheckButton);
|
|
|
|
DeleteDC(hdcMem);
|
|
|
|
/* Delete bitmaps */
|
|
DeleteObject(hDefaultTopicBitmap);
|
|
DeleteObject(hTitleBitmap);
|
|
for (i = 0; i < dwNumberTopics; i++)
|
|
{
|
|
if (pTopics[i]->hBitmap)
|
|
DeleteObject(pTopics[i]->hBitmap);
|
|
}
|
|
|
|
DeleteObject(hFontTopicTitle);
|
|
DeleteObject(hFontTopicDescription);
|
|
DeleteObject(hFontTopicButton);
|
|
|
|
if (hFontCheckButton)
|
|
DeleteObject(hFontCheckButton);
|
|
|
|
DeleteObject(hbrLightBlue);
|
|
DeleteObject(hbrDarkBlue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
INT_PTR CALLBACK
|
|
MainWndProc(HWND hWnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
return OnCreate(hWnd, wParam, lParam);
|
|
|
|
case WM_COMMAND:
|
|
return OnCommand(hWnd, wParam, lParam);
|
|
|
|
case WM_ACTIVATE:
|
|
return OnActivate(hWnd, wParam, lParam);
|
|
|
|
case WM_PAINT:
|
|
return OnPaint(hWnd, wParam, lParam);
|
|
|
|
case WM_DRAWITEM:
|
|
return OnDrawItem(hWnd, wParam, lParam);
|
|
|
|
case WM_CTLCOLORSTATIC:
|
|
return OnCtlColorStatic(hWnd, wParam, lParam);
|
|
|
|
case WM_MOUSEMOVE:
|
|
return OnMouseMove(hWnd, wParam, lParam);
|
|
|
|
case WM_DESTROY:
|
|
OnDestroy(hWnd, wParam, lParam);
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
/* EOF */
|