1 /* 2 * PROJECT: ReactOS Character Map 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: base/applications/charmap/map.c 5 * PURPOSE: class implementation for painting glyph region 6 * COPYRIGHT: Copyright 2007 Ged Murphy <gedmurphy@reactos.org> 7 * 8 */ 9 10 #include "precomp.h" 11 12 #include <stdlib.h> 13 #include <winnls.h> 14 15 static const WCHAR szMapWndClass[] = L"FontMapWnd"; 16 static const WCHAR szLrgCellWndClass[] = L"LrgCellWnd"; 17 18 #define MAX_ROWS (0xFFFF / XCELLS) + 1 - YCELLS 19 20 21 static 22 VOID 23 SetGrid(PMAP infoPtr) 24 { 25 INT x, y; 26 27 for (y = 0; y < YCELLS; y++) 28 for (x = 0; x < XCELLS; x++) 29 { 30 infoPtr->Cells[y][x].CellExt.left = x * infoPtr->CellSize.cx + 1; 31 infoPtr->Cells[y][x].CellExt.top = y * infoPtr->CellSize.cy + 1; 32 infoPtr->Cells[y][x].CellExt.right = (x + 1) * infoPtr->CellSize.cx + 2; 33 infoPtr->Cells[y][x].CellExt.bottom = (y + 1) * infoPtr->CellSize.cy + 2; 34 35 CopyRect(&infoPtr->Cells[y][x].CellInt, 36 &infoPtr->Cells[y][x].CellExt); 37 38 InflateRect(&infoPtr->Cells[y][x].CellInt, 39 -1, 40 -1); 41 } 42 } 43 44 45 static 46 VOID 47 DrawActiveCell(PMAP infoPtr, 48 HDC hdc) 49 { 50 Rectangle(hdc, 51 infoPtr->pActiveCell->CellInt.left, 52 infoPtr->pActiveCell->CellInt.top, 53 infoPtr->pActiveCell->CellInt.right, 54 infoPtr->pActiveCell->CellInt.bottom); 55 56 } 57 58 59 static 60 VOID 61 DrawGrid(PMAP infoPtr, 62 PAINTSTRUCT *ps) 63 { 64 INT x, y; 65 RECT rc; 66 PCELL Cell; 67 68 for (y = 0; y < YCELLS; y++) 69 for (x = 0; x < XCELLS; x++) 70 { 71 Cell = &infoPtr->Cells[y][x]; 72 73 if (!IntersectRect(&rc, 74 &ps->rcPaint, 75 &Cell->CellExt)) 76 { 77 continue; 78 } 79 80 Rectangle(ps->hdc, 81 Cell->CellExt.left, 82 Cell->CellExt.top, 83 Cell->CellExt.right, 84 Cell->CellExt.bottom); 85 86 if (infoPtr->pActiveCell == Cell) 87 { 88 DrawActiveCell(infoPtr, ps->hdc); 89 } 90 } 91 } 92 93 94 static 95 VOID 96 FillGrid(PMAP infoPtr, 97 PAINTSTRUCT *ps) 98 { 99 HFONT hOldFont; 100 WCHAR ch; 101 INT x, y; 102 RECT rc; 103 PCELL Cell; 104 INT i, added; 105 106 hOldFont = SelectObject(ps->hdc, 107 infoPtr->hFont); 108 109 i = XCELLS * infoPtr->iYStart; 110 111 added = 0; 112 113 for (y = 0; y < YCELLS; y++) 114 for (x = 0; x < XCELLS; x++) 115 { 116 if (i >= infoPtr->NumValidGlyphs) break; 117 118 ch = (WCHAR)infoPtr->ValidGlyphs[i]; 119 120 Cell = &infoPtr->Cells[y][x]; 121 122 if (IntersectRect(&rc, 123 &ps->rcPaint, 124 &Cell->CellExt)) 125 { 126 Cell->ch = ch; 127 128 DrawTextW(ps->hdc, 129 &ch, 130 1, 131 &Cell->CellInt, 132 DT_CENTER | DT_VCENTER | DT_SINGLELINE); 133 134 added++; 135 } 136 137 i++; 138 ch = (WCHAR)i; 139 } 140 SelectObject(ps->hdc, 141 hOldFont); 142 } 143 144 145 static 146 BOOL 147 CreateLargeCell(PMAP infoPtr) 148 { 149 RECT rLarge; 150 151 CopyRect(&rLarge, 152 &infoPtr->pActiveCell->CellExt); 153 154 MapWindowPoints(infoPtr->hMapWnd, 155 infoPtr->hParent, 156 (VOID*)&rLarge, 157 2); 158 159 InflateRect(&rLarge, 160 XLARGE - XCELLS, 161 YLARGE - YCELLS); 162 163 infoPtr->hLrgWnd = CreateWindowExW(0, 164 szLrgCellWndClass, 165 NULL, 166 WS_CHILDWINDOW | WS_VISIBLE, 167 rLarge.left, 168 rLarge.top, 169 rLarge.right - rLarge.left, 170 rLarge.bottom - rLarge.top, 171 infoPtr->hParent, 172 NULL, 173 hInstance, 174 infoPtr); 175 if (!infoPtr->hLrgWnd) 176 return FALSE; 177 178 return TRUE; 179 } 180 181 182 static 183 VOID 184 MoveLargeCell(PMAP infoPtr) 185 { 186 RECT rLarge; 187 188 CopyRect(&rLarge, 189 &infoPtr->pActiveCell->CellExt); 190 191 MapWindowPoints(infoPtr->hMapWnd, 192 infoPtr->hParent, 193 (VOID*)&rLarge, 194 2); 195 196 InflateRect(&rLarge, 197 XLARGE - XCELLS, 198 YLARGE - YCELLS); 199 200 MoveWindow(infoPtr->hLrgWnd, 201 rLarge.left, 202 rLarge.top, 203 rLarge.right - rLarge.left, 204 rLarge.bottom - rLarge.top, 205 TRUE); 206 207 InvalidateRect(infoPtr->hLrgWnd, 208 NULL, 209 TRUE); 210 } 211 212 213 static 214 VOID 215 GetPossibleCharacters(WCHAR* ch, INT chLen, INT codePageIdx) 216 { 217 INT i, j; 218 219 memset(ch, 0, sizeof(ch[0]) * chLen); 220 221 if (codePageIdx <= 0 || codePageIdx > SIZEOF(codePages)) 222 { 223 /* this is unicode, so just load up the first MAX_GLYPHS characters 224 start at 0x21 to bypass whitespace characters */ 225 INT len = min(MAX_GLYPHS, chLen); 226 for (i = 0x21, j = 0; i < len; i++) 227 ch[j++] = (WCHAR)i; 228 } 229 else 230 { 231 /* This is a codepage, so use NLS to translate the first 256 characters */ 232 CHAR multiByteString[256] = { 0 }; 233 for (i = 0x21; i < SIZEOF(multiByteString); i++) 234 multiByteString[i] = (CHAR)i; 235 236 if (!MultiByteToWideChar(codePages[codePageIdx - 1], 0, multiByteString, sizeof(multiByteString), ch, chLen)) 237 { 238 /* Failed for some reason, so clear the array */ 239 memset(ch, 0, sizeof(ch[0]) * chLen); 240 } 241 } 242 } 243 244 245 static 246 VOID 247 SetFont(PMAP infoPtr, 248 LPWSTR lpFontName) 249 { 250 HDC hdc; 251 WCHAR ch[MAX_GLYPHS]; 252 WORD out[MAX_GLYPHS]; 253 DWORD i, j; 254 255 /* Destroy Zoom window, since it was created with older font */ 256 DestroyWindow(infoPtr->hLrgWnd); 257 infoPtr->hLrgWnd = NULL; 258 259 if (infoPtr->hFont) 260 DeleteObject(infoPtr->hFont); 261 262 ZeroMemory(&infoPtr->CurrentFont, 263 sizeof(LOGFONTW)); 264 265 hdc = GetDC(infoPtr->hMapWnd); 266 infoPtr->CurrentFont.lfHeight = GetDeviceCaps(hdc, LOGPIXELSY) / 5; 267 268 infoPtr->CurrentFont.lfCharSet = DEFAULT_CHARSET; 269 lstrcpynW(infoPtr->CurrentFont.lfFaceName, 270 lpFontName, 271 SIZEOF(infoPtr->CurrentFont.lfFaceName)); 272 273 infoPtr->hFont = CreateFontIndirectW(&infoPtr->CurrentFont); 274 275 InvalidateRect(infoPtr->hMapWnd, 276 NULL, 277 TRUE); 278 279 if (infoPtr->pActiveCell) 280 infoPtr->pActiveCell->bActive = FALSE; 281 infoPtr->pActiveCell = &infoPtr->Cells[0][0]; 282 infoPtr->pActiveCell->bActive = TRUE; 283 284 // Get all the valid glyphs in this font 285 286 SelectObject(hdc, infoPtr->hFont); 287 288 // Get the code page associated with the selected 'character set' 289 GetPossibleCharacters(ch, MAX_GLYPHS, infoPtr->CharMap); 290 291 if (GetGlyphIndicesW(hdc, 292 ch, 293 MAX_GLYPHS, 294 out, 295 GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) 296 { 297 j = 0; 298 for (i = 0; i < MAX_GLYPHS; i++) 299 { 300 if (out[i] != 0xffff && out[i] != 0x0000 && ch[i] != 0x0000) 301 { 302 infoPtr->ValidGlyphs[j] = ch[i]; 303 j++; 304 } 305 } 306 infoPtr->NumValidGlyphs = j; 307 } 308 309 ReleaseDC(infoPtr->hMapWnd, hdc); 310 311 infoPtr->NumRows = infoPtr->NumValidGlyphs / XCELLS; 312 if (infoPtr->NumValidGlyphs % XCELLS) 313 infoPtr->NumRows += 1; 314 infoPtr->NumRows = (infoPtr->NumRows > YCELLS) ? infoPtr->NumRows - YCELLS : 0; 315 316 SetScrollRange(infoPtr->hMapWnd, SB_VERT, 0, infoPtr->NumRows, FALSE); 317 SetScrollPos(infoPtr->hMapWnd, SB_VERT, 0, TRUE); 318 infoPtr->iYStart = 0; 319 } 320 321 322 static 323 LRESULT 324 NotifyParentOfSelection(PMAP infoPtr, 325 UINT code, 326 WCHAR ch) 327 { 328 LRESULT Ret = 0; 329 330 if (infoPtr->hParent != NULL) 331 { 332 DWORD dwIdc = GetWindowLongPtr(infoPtr->hMapWnd, GWLP_ID); 333 /* 334 * Push directly into the event queue instead of waiting 335 * the parent to be unlocked. 336 * High word of LPARAM is still available for future needs... 337 */ 338 Ret = PostMessage(infoPtr->hParent, 339 WM_COMMAND, 340 MAKELPARAM((WORD)dwIdc, (WORD)code), 341 (LPARAM)LOWORD(ch)); 342 } 343 344 return Ret; 345 } 346 347 348 static 349 VOID 350 OnClick(PMAP infoPtr, 351 WORD ptx, 352 WORD pty) 353 { 354 INT x, y, i; 355 356 /* 357 * Find the cell the mouse pointer is over. 358 * Since each cell is the same size, this can be done quickly using CellSize. 359 * Clamp to XCELLS - 1 and YCELLS - 1 because the map can sometimes be slightly 360 * larger than infoPtr.CellSize * XCELLS , due to the map size being a non integer 361 * multiple of infoPtr.CellSize . 362 */ 363 x = min(XCELLS - 1, ptx / max(1, infoPtr->CellSize.cx)); 364 y = min(YCELLS - 1, pty / max(1, infoPtr->CellSize.cy)); 365 366 /* Make sure the mouse is within a valid glyph */ 367 i = XCELLS * infoPtr->iYStart + y * XCELLS + x; 368 if (i >= infoPtr->NumValidGlyphs) 369 { 370 if (infoPtr->pActiveCell) 371 infoPtr->pActiveCell->bActive = FALSE; 372 infoPtr->pActiveCell = NULL; 373 return; 374 } 375 376 /* if the cell is not already active */ 377 if (!infoPtr->Cells[y][x].bActive) 378 { 379 /* set previous active cell to inactive */ 380 if (infoPtr->pActiveCell) 381 { 382 /* invalidate normal cells, required when 383 * moving a small active cell via keyboard */ 384 if (!infoPtr->pActiveCell->bLarge) 385 { 386 InvalidateRect(infoPtr->hMapWnd, 387 &infoPtr->pActiveCell->CellInt, 388 TRUE); 389 } 390 391 infoPtr->pActiveCell->bActive = FALSE; 392 infoPtr->pActiveCell->bLarge = FALSE; 393 } 394 395 /* set new cell to active */ 396 infoPtr->pActiveCell = &infoPtr->Cells[y][x]; 397 infoPtr->pActiveCell->bActive = TRUE; 398 infoPtr->pActiveCell->bLarge = TRUE; 399 if (infoPtr->hLrgWnd) 400 MoveLargeCell(infoPtr); 401 else 402 CreateLargeCell(infoPtr); 403 } 404 } 405 406 407 static 408 BOOL 409 MapOnCreate(PMAP infoPtr, 410 HWND hwnd, 411 HWND hParent) 412 { 413 RECT rc; 414 BOOL Ret = FALSE; 415 416 infoPtr = HeapAlloc(GetProcessHeap(), 417 0, 418 sizeof(MAP)); 419 if (infoPtr) 420 { 421 SetLastError(0); 422 SetWindowLongPtrW(hwnd, 423 0, 424 (DWORD_PTR)infoPtr); 425 if (GetLastError() == 0) 426 { 427 ZeroMemory(infoPtr, 428 sizeof(MAP)); 429 430 infoPtr->hMapWnd = hwnd; 431 infoPtr->hParent = hParent; 432 433 GetClientRect(hwnd, &rc); 434 infoPtr->ClientSize.cx = rc.right; 435 infoPtr->ClientSize.cy = rc.bottom; 436 infoPtr->CellSize.cx = infoPtr->ClientSize.cx / XCELLS; 437 infoPtr->CellSize.cy = infoPtr->ClientSize.cy / YCELLS; 438 439 infoPtr->pActiveCell = NULL; 440 441 SetGrid(infoPtr); 442 443 SetScrollPos(infoPtr->hParent, SB_VERT, 0, TRUE); 444 445 Ret = TRUE; 446 } 447 } 448 449 return Ret; 450 } 451 452 453 static 454 VOID 455 OnVScroll(PMAP infoPtr, 456 INT Value, 457 INT Pos) 458 { 459 INT iYDiff, iOldYStart = infoPtr->iYStart; 460 461 switch (Value) 462 { 463 case SB_LINEUP: 464 infoPtr->iYStart -= 1; 465 break; 466 467 case SB_LINEDOWN: 468 infoPtr->iYStart += 1; 469 break; 470 471 case SB_PAGEUP: 472 infoPtr->iYStart -= YCELLS; 473 break; 474 475 case SB_PAGEDOWN: 476 infoPtr->iYStart += YCELLS; 477 break; 478 479 case SB_THUMBTRACK: 480 infoPtr->iYStart = Pos; 481 break; 482 483 default: 484 break; 485 } 486 487 infoPtr->iYStart = max(0, infoPtr->iYStart); 488 infoPtr->iYStart = min(infoPtr->iYStart, infoPtr->NumRows); 489 490 iYDiff = iOldYStart - infoPtr->iYStart; 491 if (iYDiff) 492 { 493 if (infoPtr->hLrgWnd != NULL) 494 { 495 ShowWindow(infoPtr->hLrgWnd, SW_HIDE); 496 } 497 498 SetScrollPos(infoPtr->hMapWnd, 499 SB_VERT, 500 infoPtr->iYStart, 501 TRUE); 502 503 if (abs(iYDiff) < YCELLS) 504 { 505 RECT rect; 506 507 /* Invalidate the rect around the active cell since a new cell will become active */ 508 if (infoPtr->pActiveCell && infoPtr->pActiveCell->bActive) 509 { 510 InvalidateRect(infoPtr->hMapWnd, 511 &infoPtr->pActiveCell->CellExt, 512 TRUE); 513 } 514 515 GetClientRect(infoPtr->hMapWnd, &rect); 516 rect.top += 2; 517 rect.bottom -= 2; 518 ScrollWindowEx(infoPtr->hMapWnd, 519 0, 520 iYDiff * infoPtr->CellSize.cy, 521 &rect, 522 &rect, 523 NULL, 524 NULL, 525 SW_INVALIDATE); 526 } 527 else 528 { 529 InvalidateRect(infoPtr->hMapWnd, 530 NULL, 531 TRUE); 532 } 533 534 if (infoPtr->hLrgWnd != NULL) 535 { 536 ShowWindow(infoPtr->hLrgWnd, SW_SHOW); 537 } 538 } 539 } 540 541 542 static 543 VOID 544 OnPaint(PMAP infoPtr, 545 WPARAM wParam) 546 { 547 PAINTSTRUCT ps; 548 HDC hdc; 549 550 551 if (wParam != 0) 552 { 553 if (!GetUpdateRect(infoPtr->hMapWnd, 554 &ps.rcPaint, 555 TRUE)) 556 { 557 return; 558 } 559 ps.hdc = (HDC)wParam; 560 } 561 else 562 { 563 hdc = BeginPaint(infoPtr->hMapWnd, 564 &ps); 565 if (hdc == NULL) 566 { 567 return; 568 } 569 } 570 571 DrawGrid(infoPtr, &ps); 572 573 FillGrid(infoPtr, &ps); 574 575 if (wParam == 0) 576 { 577 EndPaint(infoPtr->hMapWnd, 578 &ps); 579 } 580 } 581 582 583 LRESULT 584 CALLBACK 585 MapWndProc(HWND hwnd, 586 UINT uMsg, 587 WPARAM wParam, 588 LPARAM lParam) 589 { 590 PMAP infoPtr; 591 LRESULT Ret = 0; 592 WCHAR lfFaceName[LF_FACESIZE]; 593 594 infoPtr = (PMAP)GetWindowLongPtrW(hwnd, 595 0); 596 597 switch (uMsg) 598 { 599 case WM_CREATE: 600 { 601 if (!MapOnCreate(infoPtr, 602 hwnd, 603 ((LPCREATESTRUCTW)lParam)->hwndParent)) 604 { 605 return (LRESULT)-1; 606 } 607 608 break; 609 } 610 611 case WM_LBUTTONDOWN: 612 { 613 OnClick(infoPtr, 614 LOWORD(lParam), 615 HIWORD(lParam)); 616 617 break; 618 } 619 620 case WM_MOUSEMOVE: 621 { 622 if (wParam & MK_LBUTTON) 623 { 624 OnClick(infoPtr, 625 LOWORD(lParam), 626 HIWORD(lParam)); 627 } 628 break; 629 } 630 631 case WM_LBUTTONDBLCLK: 632 { 633 if (!infoPtr->pActiveCell) 634 break; 635 636 NotifyParentOfSelection(infoPtr, 637 FM_SETCHAR, 638 infoPtr->pActiveCell->ch); 639 640 if (infoPtr->pActiveCell->bLarge) 641 { 642 DestroyWindow(infoPtr->hLrgWnd); 643 infoPtr->hLrgWnd = NULL; 644 } 645 646 infoPtr->pActiveCell->bLarge = FALSE; 647 648 break; 649 } 650 651 case WM_VSCROLL: 652 { 653 OnVScroll(infoPtr, 654 LOWORD(wParam), 655 HIWORD(wParam)); 656 657 break; 658 } 659 660 case FM_SETCHARMAP: 661 infoPtr->CharMap = LOWORD(wParam); 662 wcsncpy(lfFaceName, 663 infoPtr->CurrentFont.lfFaceName, 664 SIZEOF(lfFaceName)); 665 SetFont(infoPtr, lfFaceName); 666 break; 667 668 case FM_SETFONT: 669 SetFont(infoPtr, (LPWSTR)lParam); 670 break; 671 672 case FM_GETCHAR: 673 { 674 if (!infoPtr->pActiveCell) return 0; 675 return infoPtr->pActiveCell->ch; 676 } 677 678 case FM_GETHFONT: 679 return (LRESULT)infoPtr->hFont; 680 681 case WM_PAINT: 682 { 683 OnPaint(infoPtr, 684 wParam); 685 break; 686 } 687 688 case WM_DESTROY: 689 { 690 DeleteObject(infoPtr->hFont); 691 HeapFree(GetProcessHeap(), 692 0, 693 infoPtr); 694 SetWindowLongPtrW(hwnd, 695 0, 696 (DWORD_PTR)NULL); 697 break; 698 } 699 700 default: 701 { 702 Ret = DefWindowProcW(hwnd, 703 uMsg, 704 wParam, 705 lParam); 706 break; 707 } 708 } 709 710 return Ret; 711 } 712 713 714 BOOL 715 RegisterMapClasses(HINSTANCE hInstance) 716 { 717 WNDCLASSW wc = {0}; 718 719 wc.style = CS_DBLCLKS; 720 wc.lpfnWndProc = MapWndProc; 721 wc.cbWndExtra = sizeof(PMAP); 722 wc.hInstance = hInstance; 723 wc.hCursor = LoadCursorW(NULL, 724 (LPWSTR)IDC_ARROW); 725 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 726 wc.lpszClassName = szMapWndClass; 727 728 if (RegisterClassW(&wc)) 729 { 730 wc.lpfnWndProc = LrgCellWndProc; 731 wc.cbWndExtra = 0; 732 wc.lpszClassName = szLrgCellWndClass; 733 734 return RegisterClassW(&wc) != 0; 735 } 736 737 return FALSE; 738 } 739 740 VOID 741 UnregisterMapClasses(HINSTANCE hInstance) 742 { 743 UnregisterClassW(szMapWndClass, 744 hInstance); 745 746 UnregisterClassW(szLrgCellWndClass, 747 hInstance); 748 } 749