reactos/base/applications/charmap/charmap.c
Katayama Hirofumi MZ 7e9f1e67f3
[CHARMAP] Improve keyboard usability (#4839)
- Use IsDialogMessage function in message loop to enable Tab.
- Add WS_TABSTOP style to IDC_FONTCOMBO control.
- Set focus on create.
- Handle WM_KEYDOWN and WM_GETDLGCODE messages.
- Add UpdateCells, LimitCaretXY SetCaretXY, MoveUpDown, and MoveLeftRight helper functions.
- Delete DrawGrid, and DrawActiveCell functions for simplicity.
- Add and delete some members in MAP structure.
CORE-13806
2022-11-06 08:37:18 +09:00

733 lines
18 KiB
C

/*
* PROJECT: ReactOS Character Map
* LICENSE: GPL - See COPYING in the top level directory
* FILE: base/applications/charmap/charmap.c
* PURPOSE: main dialog implementation
* COPYRIGHT: Copyright 2007 Ged Murphy <gedmurphy@reactos.org>
*/
#include "precomp.h"
#include <commctrl.h>
#include <richedit.h>
#include <winnls.h>
//#define REMOVE_ADVANCED
#define ID_ABOUT 0x1
HINSTANCE hInstance;
HWND hAdvancedDlg;
HWND hCharmapDlg;
HWND hStatusWnd;
HICON hSmIcon;
HICON hBgIcon;
SETTINGS Settings;
static
VOID
FillCharacterSetComboList(HWND hwndCombo)
{
WCHAR szCharSetText[256];
LPWSTR trimmedName;
CPINFOEXW cpInfo;
INT i;
if (LoadStringW(hInstance, IDS_UNICODE, szCharSetText, SIZEOF(szCharSetText)))
{
SendMessageW(hwndCombo,
CB_ADDSTRING,
0,
(LPARAM)szCharSetText);
}
for (i = 0; i < SIZEOF(codePages); i++)
{
if (GetCPInfoExW(codePages[i], 0, &cpInfo))
{
trimmedName = wcschr(cpInfo.CodePageName, L'(');
if (!trimmedName)
trimmedName = cpInfo.CodePageName;
SendMessageW(hwndCombo,
CB_ADDSTRING,
0,
(LPARAM)trimmedName);
}
}
SendMessageW(hwndCombo, CB_SETCURSEL, 0, 0);
}
static
VOID
FillGroupByComboList(HWND hwndCombo)
{
WCHAR szAllText[256];
if (LoadStringW(hInstance, IDS_ALL, szAllText, SIZEOF(szAllText)))
{
SendMessageW(hwndCombo, CB_ADDSTRING, 0, (LPARAM)szAllText);
}
SendMessageW(hwndCombo, CB_SETCURSEL, 0, 0);
}
/* Font-enumeration callback */
static
int
CALLBACK
EnumFontNames(ENUMLOGFONTEXW *lpelfe,
NEWTEXTMETRICEXW *lpntme,
DWORD FontType,
LPARAM lParam)
{
HWND hwndCombo = (HWND)lParam;
LPWSTR pszName = lpelfe->elfLogFont.lfFaceName;
/* Skip rotated font */
if(pszName[0] == L'@') return 1;
/* make sure font doesn't already exist in our list */
if(SendMessageW(hwndCombo,
CB_FINDSTRINGEXACT,
0,
(LPARAM)pszName) == CB_ERR)
{
INT idx;
BOOL fFixed;
BOOL fTrueType;
/* add the font */
idx = (INT)SendMessageW(hwndCombo,
CB_ADDSTRING,
0,
(LPARAM)pszName);
/* record the font's attributes (Fixedwidth and Truetype) */
fFixed = (lpelfe->elfLogFont.lfPitchAndFamily & FIXED_PITCH) ? TRUE : FALSE;
fTrueType = (lpelfe->elfLogFont.lfOutPrecision == OUT_STROKE_PRECIS) ? TRUE : FALSE;
/* store this information in the list-item's userdata area */
SendMessageW(hwndCombo,
CB_SETITEMDATA,
idx,
MAKEWPARAM(fFixed, fTrueType));
}
return 1;
}
/* Initialize the font-list by enumeration all system fonts */
static
VOID
FillFontStyleComboList(HWND hwndCombo)
{
HDC hdc;
LOGFONTW lf;
/* FIXME: for fun, draw each font in its own style */
HFONT hFont = GetStockObject(DEFAULT_GUI_FONT);
SendMessageW(hwndCombo,
WM_SETFONT,
(WPARAM)hFont,
0);
ZeroMemory(&lf, sizeof(lf));
lf.lfCharSet = DEFAULT_CHARSET;
hdc = GetDC(hwndCombo);
/* store the list of fonts in the combo */
EnumFontFamiliesExW(hdc,
&lf,
(FONTENUMPROCW)EnumFontNames,
(LPARAM)hwndCombo,
0);
ReleaseDC(hwndCombo,
hdc);
SendMessageW(hwndCombo,
CB_SETCURSEL,
0,
0);
}
extern
VOID
ChangeMapFont(HWND hDlg)
{
HWND hCombo;
HWND hMap;
LPWSTR lpFontName;
INT Len;
hCombo = GetDlgItem(hDlg, IDC_FONTCOMBO);
Len = GetWindowTextLengthW(hCombo);
if (Len != 0)
{
lpFontName = HeapAlloc(GetProcessHeap(),
0,
(Len + 1) * sizeof(WCHAR));
if (lpFontName)
{
SendMessageW(hCombo,
WM_GETTEXT,
Len + 1,
(LPARAM)lpFontName);
hMap = GetDlgItem(hDlg, IDC_FONTMAP);
SendMessageW(hMap,
FM_SETFONT,
0,
(LPARAM)lpFontName);
}
HeapFree(GetProcessHeap(),
0,
lpFontName);
}
}
// Copy collected characters into the clipboard
static
void
CopyCharacters(HWND hDlg)
{
HWND hText = GetDlgItem(hDlg, IDC_TEXTBOX);
DWORD dwStart, dwEnd;
// Acquire selection limits
SendMessage(hText, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
// Test if the whose text is unselected
if(dwStart == dwEnd) {
// Select the whole text
SendMessageW(hText, EM_SETSEL, 0, -1);
// Copy text
SendMessageW(hText, WM_COPY, 0, 0);
// Restore previous values
SendMessageW(hText, EM_SETSEL, (WPARAM)dwStart, (LPARAM)dwEnd);
} else {
// Copy text
SendMessageW(hText, WM_COPY, 0, 0);
}
}
// Recover charset for the given font
static
BYTE
GetFontMetrics(HWND hWnd, HFONT hFont)
{
TEXTMETRIC tmFont;
HGDIOBJ hOldObj;
HDC hDC;
hDC = GetDC(hWnd);
hOldObj = SelectObject(hDC, hFont);
GetTextMetrics(hDC, &tmFont);
SelectObject(hDC, hOldObj);
ReleaseDC(hWnd, hDC);
return tmFont.tmCharSet;
}
// Select a new character
static
VOID
AddCharToSelection(HWND hDlg, WCHAR ch)
{
HWND hMap = GetDlgItem(hDlg, IDC_FONTMAP);
HWND hText = GetDlgItem(hDlg, IDC_TEXTBOX);
HFONT hFont;
LOGFONT lFont;
CHARFORMAT cf;
// Retrieve current character selected
if (ch == 0)
{
ch = (WCHAR) SendMessageW(hMap, FM_GETCHAR, 0, 0);
if (!ch)
return;
}
// Retrieve current selected font
hFont = (HFONT)SendMessage(hMap, FM_GETHFONT, 0, 0);
// Recover LOGFONT structure from hFont
if (!GetObject(hFont, sizeof(LOGFONT), &lFont))
return;
// Recover font properties of Richedit control
ZeroMemory(&cf, sizeof(cf));
cf.cbSize = sizeof(cf);
SendMessage(hText, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
// Apply properties of the new font
cf.bCharSet = GetFontMetrics(hText, hFont);
// Update font name
wcscpy(cf.szFaceName, lFont.lfFaceName);
// Update font properties
SendMessage(hText, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
// Send selected character to Richedit
SendMessage(hText, WM_CHAR, (WPARAM)ch, 0);
}
#ifndef REMOVE_ADVANCED
static
void
UpdateSettings(HWND hDlg)
{
if (hDlg == hCharmapDlg)
{
Settings.IsAdvancedView =
SendDlgItemMessage(hDlg, IDC_CHECK_ADVANCED, BM_GETCHECK, 0, 0);
}
if (hDlg == hAdvancedDlg)
{
}
}
#endif
VOID
UpdateStatusBar(WCHAR wch)
{
WCHAR buff[MAX_PATH];
WCHAR szDesc[MAX_PATH];
GetUName(wch, szDesc);
wsprintfW(buff, L"U+%04X: %s", wch, szDesc);
SendMessageW(hStatusWnd, SB_SETTEXT, 0, (LPARAM)buff);
}
static
void
ChangeView(HWND hWnd)
{
RECT rcCharmap;
#ifndef REMOVE_ADVANCED
RECT rcAdvanced;
#else
RECT rcCopy;
#endif
RECT rcPanelExt;
RECT rcPanelInt;
RECT rcStatus;
UINT DeX, DeY;
LONG xPos, yPos;
UINT Width, Height;
UINT DeskTopWidth, DeskTopHeight;
#ifdef REMOVE_ADVANCED
HWND hCopy;
#endif
GetClientRect(hCharmapDlg, &rcCharmap);
#ifndef REMOVE_ADVANCED
GetClientRect(hAdvancedDlg, &rcAdvanced);
#else
hCopy = GetDlgItem(hCharmapDlg, IDC_COPY);
GetClientRect(hCopy, &rcCopy);
#endif
GetWindowRect(hWnd, &rcPanelExt);
GetClientRect(hWnd, &rcPanelInt);
GetClientRect(hStatusWnd, &rcStatus);
DeskTopWidth = GetSystemMetrics(SM_CXFULLSCREEN);
DeskTopHeight = GetSystemMetrics(SM_CYFULLSCREEN);
DeX = (rcPanelExt.right - rcPanelExt.left) - rcPanelInt.right;
DeY = (rcPanelExt.bottom - rcPanelExt.top) - rcPanelInt.bottom;
MoveWindow(hCharmapDlg, 0, 0, rcCharmap.right, rcCharmap.bottom, FALSE);
#ifndef REMOVE_ADVANCED
MoveWindow(hAdvancedDlg, 0, rcCharmap.bottom, rcAdvanced.right, rcAdvanced.bottom, FALSE);
ShowWindow(hAdvancedDlg, (Settings.IsAdvancedView) ? SW_SHOW : SW_HIDE);
#endif
xPos = rcPanelExt.left;
yPos = rcPanelExt.top;
Width = DeX + rcCharmap.right;
Height = DeY + rcCharmap.bottom + rcStatus.bottom;
#ifndef REMOVE_ADVANCED
if (Settings.IsAdvancedView)
Height += rcAdvanced.bottom;
#else
/* The lack of advanced button leaves an empty gap at the bottom of the window.
Shrink the window height a bit here to accomodate for that lost control. */
Height = rcCharmap.bottom + rcCopy.bottom + 10;
#endif
// FIXME: This fails on multi monitor setups
if ((xPos + Width) > DeskTopWidth)
xPos = DeskTopWidth - Width;
if ((yPos + Height) > DeskTopHeight)
yPos = DeskTopHeight - Height;
MoveWindow(hWnd,
xPos, yPos,
Width, Height,
TRUE);
}
static
INT_PTR
CALLBACK
CharMapDlgProc(HWND hDlg,
UINT Message,
WPARAM wParam,
LPARAM lParam)
{
switch(Message)
{
case WM_INITDIALOG:
{
DWORD evMask;
#ifdef REMOVE_ADVANCED
HWND hAdv;
#endif
FillFontStyleComboList(GetDlgItem(hDlg,
IDC_FONTCOMBO));
ChangeMapFont(hDlg);
// Configure Richedit control for sending notification changes.
evMask = SendDlgItemMessage(hDlg, IDC_TEXTBOX, EM_GETEVENTMASK, 0, 0);
evMask |= ENM_CHANGE;
SendDlgItemMessage(hDlg, IDC_TEXTBOX, EM_SETEVENTMASK, 0, (LPARAM)evMask);
#ifdef REMOVE_ADVANCED
hAdv = GetDlgItem(hDlg, IDC_CHECK_ADVANCED);
ShowWindow(hAdv, SW_HIDE);
#endif
return TRUE;
}
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case IDC_FONTMAP:
switch (HIWORD(wParam))
{
case FM_SETCHAR:
AddCharToSelection(hDlg, LOWORD(lParam));
break;
}
break;
case IDC_FONTCOMBO:
if (HIWORD(wParam) == CBN_SELCHANGE)
{
ChangeMapFont(hDlg);
}
break;
case IDC_SELECT:
AddCharToSelection(hDlg, 0);
break;
case IDC_TEXTBOX:
switch (HIWORD(wParam)) {
case EN_CHANGE:
if (GetWindowTextLength(GetDlgItem(hDlg, IDC_TEXTBOX)) == 0)
EnableWindow(GetDlgItem(hDlg, IDC_COPY), FALSE);
else
EnableWindow(GetDlgItem(hDlg, IDC_COPY), TRUE);
break;
}
break;
case IDC_COPY:
CopyCharacters(hDlg);
break;
#ifndef REMOVE_ADVANCED
case IDC_CHECK_ADVANCED:
UpdateSettings(hDlg);
ChangeView(GetParent(hDlg));
break;
#endif
}
}
break;
default:
break;
}
return FALSE;
}
#ifndef REMOVE_ADVANCED
static
INT_PTR
CALLBACK
AdvancedDlgProc(HWND hDlg,
UINT Message,
WPARAM wParam,
LPARAM lParam)
{
switch(Message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_COMBO_CHARSET:
if (HIWORD(wParam) == CBN_SELCHANGE)
{
INT idx = (INT)SendMessageW((HWND)lParam,
CB_GETCURSEL,
0, 0);
SendMessageW(GetDlgItem(hCharmapDlg, IDC_FONTMAP),
FM_SETCHARMAP,
idx, 0);
EnableWindow(GetDlgItem(hAdvancedDlg, IDC_EDIT_UNICODE), idx == 0);
}
break;
}
}
default:
return FALSE;
}
return FALSE;
}
#endif
static int
PanelOnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
HMENU hSysMenu;
WCHAR lpAboutText[256];
hCharmapDlg = CreateDialog(hInstance,
MAKEINTRESOURCE(IDD_CHARMAP),
hWnd,
CharMapDlgProc);
// For now, the Help push button is disabled because of lacking of HTML Help support
EnableWindow(GetDlgItem(hCharmapDlg, IDC_CMHELP), FALSE);
#ifndef REMOVE_ADVANCED
hAdvancedDlg = CreateDialog(hInstance,
MAKEINTRESOURCE(IDD_ADVANCED),
hWnd,
AdvancedDlgProc);
FillCharacterSetComboList(GetDlgItem(hAdvancedDlg, IDC_COMBO_CHARSET));
FillGroupByComboList(GetDlgItem(hAdvancedDlg, IDC_COMBO_GROUPBY));
EnableWindow(GetDlgItem(hAdvancedDlg, IDC_COMBO_GROUPBY), FALSE); // FIXME: Implement
EnableWindow(GetDlgItem(hAdvancedDlg, IDC_BUTTON_SEARCH), FALSE); // FIXME: Implement
#endif
hStatusWnd = CreateWindow(STATUSCLASSNAME,
NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hWnd,
(HMENU)IDD_STATUSBAR,
hInstance,
NULL);
// Set the status bar for multiple parts output
SendMessage(hStatusWnd, SB_SIMPLE, (WPARAM)FALSE, (LPARAM)0);
ChangeView(hWnd);
hSysMenu = GetSystemMenu(hWnd, FALSE);
if (hSysMenu != NULL)
{
if (LoadStringW(hInstance, IDS_ABOUT, lpAboutText, SIZEOF(lpAboutText)))
{
AppendMenuW(hSysMenu, MF_SEPARATOR, 0, NULL);
AppendMenuW(hSysMenu, MF_STRING, ID_ABOUT, lpAboutText);
}
}
SetFocus(hCharmapDlg);
return 0;
}
static LRESULT CALLBACK
PanelWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_CREATE:
return PanelOnCreate(hWnd, wParam, lParam);
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_SIZE:
SendMessage(hStatusWnd, msg, wParam, lParam);
break;
case WM_DESTROY:
SaveSettings();
PostQuitMessage(0);
return 0;
case WM_SYSCOMMAND:
switch(wParam) {
case ID_ABOUT:
ShowAboutDlg(hWnd);
break;
}
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
static HWND
InitInstance(HINSTANCE hInst)
{
WCHAR szClass[] = L"CharMap";
WCHAR szTitle[256];
WNDCLASSEXW wc;
HWND hWnd;
LoadStringW(hInst, IDS_TITLE, szTitle, SIZEOF(szTitle));
hSmIcon = LoadImage(hInstance,
MAKEINTRESOURCE(IDI_ICON),
IMAGE_ICON,
16,
16,
0);
hBgIcon = LoadImage(hInstance,
MAKEINTRESOURCE(IDI_ICON),
IMAGE_ICON,
32,
32,
0);
// Create workspace
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = PanelWndProc;
wc.hInstance = hInst;
wc.hIcon = hBgIcon;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = szClass;
wc.hIconSm = hSmIcon;
RegisterClassExW(&wc);
hWnd = CreateWindowW(
szClass,
szTitle,
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInst,
NULL);
if (hWnd != NULL)
{
LoadSettings();
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
}
return hWnd;
}
INT
WINAPI
wWinMain(HINSTANCE hInst,
HINSTANCE hPrev,
LPWSTR Cmd,
int iCmd)
{
INITCOMMONCONTROLSEX iccx;
INT Ret = 1;
HMODULE hRichEd20;
MSG Msg;
hInstance = hInst;
/* Mirroring code for the titlebar */
switch (GetUserDefaultUILanguage())
{
case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
SetProcessDefaultLayout(LAYOUT_RTL);
break;
default:
break;
}
iccx.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccx.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&iccx);
if (RegisterMapClasses(hInstance))
{
hRichEd20 = LoadLibraryW(L"RICHED20.DLL");
if (hRichEd20 != NULL)
{
InitInstance(hInst);
for (;;)
{
if (GetMessage(&Msg, NULL, 0, 0) <= 0)
{
Ret = Msg.wParam;
break;
}
/* NOTE: CreateDialog needs IsDialogMessage call in message loop */
if (hCharmapDlg && IsDialogMessage(hCharmapDlg, &Msg))
continue;
#ifndef REMOVE_ADVANCED
if (hAdvancedDlg && IsDialogMessage(hAdvancedDlg, &Msg))
continue;
#endif
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
FreeLibrary(hRichEd20);
}
UnregisterMapClasses(hInstance);
}
return Ret;
}