1 /* 2 * PROJECT: ReactOS Character Map 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: base/applications/charmap/GridView.cpp 5 * PURPOSE: Class for for the window which contains the font matrix 6 * COPYRIGHT: Copyright 2015 Ged Murphy <gedmurphy@reactos.org> 7 */ 8 9 10 #include "precomp.h" 11 #include "GridView.h" 12 #include "Cell.h" 13 14 15 /* DATA *****************************************************/ 16 17 extern HINSTANCE g_hInstance; 18 19 20 /* PUBLIC METHODS **********************************************/ 21 22 CGridView::CGridView() : 23 m_xNumCells(20), 24 m_yNumCells(10), 25 m_ScrollPosition(0), 26 m_NumRows(0) 27 { 28 m_szMapWndClass = L"CharGridWClass"; 29 } 30 31 CGridView::~CGridView() 32 { 33 } 34 35 bool 36 CGridView::Create( 37 _In_ HWND hParent 38 ) 39 { 40 WNDCLASSW wc = { 0 }; 41 wc.style = CS_DBLCLKS; 42 wc.lpfnWndProc = MapWndProc; 43 wc.cbWndExtra = sizeof(CGridView *); 44 wc.hInstance = g_hInstance; 45 wc.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_ARROW); 46 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 47 wc.lpszClassName = m_szMapWndClass; 48 49 if (RegisterClassW(&wc)) 50 { 51 m_hwnd = CreateWindowExW(0, 52 m_szMapWndClass, 53 NULL, 54 WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL, 55 0,0,0,0, 56 hParent, 57 NULL, 58 g_hInstance, 59 this); 60 61 } 62 63 return !!(m_hwnd != NULL); 64 } 65 66 bool 67 CGridView::SetFont( 68 _In_ CAtlString& FontName 69 ) 70 { 71 72 // Create a temporary container for the new font 73 CurrentFont NewFont = { 0 }; 74 NewFont.FontName = FontName; 75 76 // Get the DC for the full grid window 77 HDC hdc; 78 hdc = GetDC(m_hwnd); 79 if (hdc == NULL) return false; 80 81 // Setup the logfont structure 82 NewFont.Font.lfHeight = 0; // This is set in WM_SIZE 83 NewFont.Font.lfCharSet = DEFAULT_CHARSET; 84 StringCchCopyW(NewFont.Font.lfFaceName, LF_FACESIZE, FontName); 85 86 // Get a handle to the new font 87 NewFont.hFont = CreateFontIndirectW(&NewFont.Font); 88 if (NewFont.hFont == NULL) 89 { 90 ReleaseDC(m_hwnd, hdc); 91 return false; 92 } 93 94 // Setup an array of all possible non-BMP indices 95 WCHAR ch[MAX_GLYPHS]; 96 for (int i = 0; i < MAX_GLYPHS; i++) 97 ch[i] = (WCHAR)i; 98 99 HFONT hOldFont; 100 hOldFont = (HFONT)SelectObject(hdc, NewFont.hFont); 101 102 // Translate all the indices into glyphs 103 WORD out[MAX_GLYPHS]; 104 DWORD Status; 105 Status = GetGlyphIndicesW(hdc, 106 ch, 107 MAX_GLYPHS, 108 out, 109 GGI_MARK_NONEXISTING_GLYPHS); 110 ReleaseDC(m_hwnd, hdc); 111 if (Status == GDI_ERROR) 112 { 113 SelectObject(hdc, hOldFont); 114 return false; 115 } 116 117 // Loop all the glyphs looking for valid ones 118 // and store those in our font data 119 int j = 0; 120 for (int i = 0; i < MAX_GLYPHS; i++) 121 { 122 if (out[i] != 0xffff) 123 { 124 NewFont.ValidGlyphs[j] = ch[i]; 125 j++; 126 } 127 } 128 NewFont.NumValidGlyphs = j; 129 130 // Calculate the number of rows required to hold all glyphs 131 m_NumRows = NewFont.NumValidGlyphs / m_xNumCells; 132 if (NewFont.NumValidGlyphs % m_xNumCells) 133 m_NumRows += 1; 134 135 // Set the scrollbar in relation to the rows 136 SetScrollRange(m_hwnd, SB_VERT, 0, m_NumRows - m_yNumCells, FALSE); 137 138 // We're done, update the current font 139 m_CurrentFont = NewFont; 140 141 // We changed the font, we'll need to repaint the whole window 142 InvalidateRect(m_hwnd, 143 NULL, 144 TRUE); 145 146 return true; 147 } 148 149 150 151 /* PRIVATE METHODS **********************************************/ 152 153 bool 154 CGridView::UpdateCellCoordinates( 155 ) 156 { 157 // Go through all the cells and calculate 158 // their coordinates within the grid 159 for (int y = 0; y < m_yNumCells; y++) 160 for (int x = 0; x < m_xNumCells; x++) 161 { 162 RECT CellCoordinates; 163 CellCoordinates.left = x * m_CellSize.cx; 164 CellCoordinates.top = y * m_CellSize.cy; 165 CellCoordinates.right = (x + 1) * m_CellSize.cx + 1; 166 CellCoordinates.bottom = (y + 1) * m_CellSize.cy + 1; 167 168 m_Cells[y][x]->SetCellCoordinates(CellCoordinates); 169 } 170 171 return true; 172 } 173 174 LRESULT 175 CGridView::OnCreate( 176 _In_ HWND hwnd, 177 _In_ HWND hParent 178 ) 179 { 180 m_hwnd = hwnd; 181 m_hParent = hParent; 182 183 // C++ doesn't allow : "CCells ***C = new CCell***[x * y]" 184 // so we have to build the 2d array up manually 185 m_Cells = new CCell**[m_yNumCells]; // rows 186 for (int i = 0; i < m_yNumCells; i++) 187 m_Cells[i] = new CCell*[m_xNumCells]; // columns 188 189 for (int y = 0; y < m_yNumCells; y++) 190 for (int x = 0; x < m_xNumCells; x++) 191 { 192 m_Cells[y][x] = new CCell(m_hwnd); 193 } 194 195 // Give the first cell focus 196 SetCellFocus(m_Cells[0][0]); 197 198 return 0; 199 } 200 201 LRESULT 202 CGridView::OnSize( 203 _In_ INT Width, 204 _In_ INT Height 205 ) 206 { 207 // Get the client area of the main dialog 208 RECT ParentRect; 209 GetClientRect(m_hParent, &ParentRect); 210 211 // Calculate the grid size using the parent 212 m_ClientCoordinates.left = ParentRect.left + 25; 213 m_ClientCoordinates.top = ParentRect.top + 50; 214 m_ClientCoordinates.right = ParentRect.right - m_ClientCoordinates.left - 10; 215 m_ClientCoordinates.bottom = ParentRect.bottom - m_ClientCoordinates.top - 70; 216 217 // Resize the grid window 218 SetWindowPos(m_hwnd, 219 NULL, 220 m_ClientCoordinates.left, 221 m_ClientCoordinates.top, 222 m_ClientCoordinates.right, 223 m_ClientCoordinates.bottom, 224 SWP_NOZORDER | SWP_SHOWWINDOW); 225 226 // Get the client area we can draw on. The position we set above includes 227 // a scrollbar which we obviously can't draw on. GetClientRect gives us 228 // the size without the scroll, and it's more efficient than getting the 229 // scroll metrics and calculating the size from that 230 RECT ClientRect; 231 GetClientRect(m_hwnd, &ClientRect); 232 m_CellSize.cx = ClientRect.right / m_xNumCells; 233 m_CellSize.cy = ClientRect.bottom / m_yNumCells; 234 235 // Let all the cells know about their new coords 236 UpdateCellCoordinates(); 237 238 // We scale the font size up or down depending on the cell size 239 if (m_CurrentFont.hFont) 240 { 241 // Delete the existing font 242 DeleteObject(m_CurrentFont.hFont); 243 244 HDC hdc; 245 hdc = GetDC(m_hwnd); 246 if (hdc) 247 { 248 // Update the font size with respect to the cell size 249 m_CurrentFont.Font.lfHeight = (m_CellSize.cy - 5); 250 m_CurrentFont.hFont = CreateFontIndirectW(&m_CurrentFont.Font); 251 ReleaseDC(m_hwnd, hdc); 252 } 253 } 254 255 // Redraw the whole grid 256 InvalidateRect(m_hwnd, &ClientRect, TRUE); 257 258 return 0; 259 } 260 261 VOID 262 CGridView::OnVScroll(_In_ INT Value, 263 _In_ INT Pos) 264 { 265 266 INT PrevScrollPosition = m_ScrollPosition; 267 268 switch (Value) 269 { 270 case SB_LINEUP: 271 m_ScrollPosition -= 1; 272 break; 273 274 case SB_LINEDOWN: 275 m_ScrollPosition += 1; 276 break; 277 278 case SB_PAGEUP: 279 m_ScrollPosition -= m_yNumCells; 280 break; 281 282 case SB_PAGEDOWN: 283 m_ScrollPosition += m_yNumCells; 284 break; 285 286 case SB_THUMBTRACK: 287 m_ScrollPosition = Pos; 288 break; 289 290 default: 291 break; 292 } 293 294 // Make sure we don't scroll past row 0 or max rows 295 m_ScrollPosition = max(0, m_ScrollPosition); 296 m_ScrollPosition = min(m_ScrollPosition, m_NumRows); 297 298 // Check if there's a difference from the previous position 299 INT ScrollDiff; 300 ScrollDiff = PrevScrollPosition - m_ScrollPosition; 301 if (ScrollDiff) 302 { 303 // Set the new scrollbar position in the scroll box 304 SetScrollPos(m_hwnd, 305 SB_VERT, 306 m_ScrollPosition, 307 TRUE); 308 309 // Check if the scrollbar has moved more than the 310 // number of visible rows (draged or paged) 311 if (abs(ScrollDiff) < m_yNumCells) 312 { 313 RECT rect; 314 GetClientRect(m_hwnd, &rect); 315 316 // Scroll the visible cells which remain within the grid 317 // and invalidate any new ones which appear from the top / bottom 318 ScrollWindowEx(m_hwnd, 319 0, 320 ScrollDiff * m_CellSize.cy, 321 &rect, 322 &rect, 323 NULL, 324 NULL, 325 SW_INVALIDATE); 326 } 327 else 328 { 329 // All the cells need to be redrawn 330 InvalidateRect(m_hwnd, 331 NULL, 332 TRUE); 333 } 334 } 335 } 336 337 LRESULT 338 CGridView::OnPaint( 339 _In_opt_ HDC hdc 340 ) 341 { 342 PAINTSTRUCT PaintStruct = { 0 }; 343 HDC LocalHdc = NULL; 344 BOOL bSuccess = FALSE; 345 346 // Check if we were passed a DC 347 if (hdc == NULL) 348 { 349 // We weren't, let's get one 350 LocalHdc = BeginPaint(m_hwnd, &PaintStruct); 351 if (LocalHdc) bSuccess = TRUE; 352 } 353 else 354 { 355 // Use the existing DC and just get the region to paint 356 bSuccess = GetUpdateRect(m_hwnd, 357 &PaintStruct.rcPaint, 358 TRUE); 359 if (bSuccess) 360 { 361 // Update the struct with the DC we were passed 362 PaintStruct.hdc = (HDC)hdc; 363 } 364 } 365 366 // Make sure we have a valid DC 367 if (bSuccess) 368 { 369 // Paint the grid and chars 370 DrawGrid(&PaintStruct); 371 372 if (LocalHdc) 373 { 374 EndPaint(m_hwnd, &PaintStruct); 375 } 376 } 377 378 return 0; 379 } 380 381 LRESULT 382 CALLBACK 383 CGridView::MapWndProc( 384 HWND hwnd, 385 UINT uMsg, 386 WPARAM wParam, 387 LPARAM lParam 388 ) 389 { 390 CGridView *This; 391 LRESULT RetCode = 0; 392 393 // Get the object pointer from window context 394 This = (CGridView *)GetWindowLongPtr(hwnd, GWLP_USERDATA); 395 if (This == NULL) 396 { 397 // Check that this isn't a create message 398 if (uMsg != WM_CREATE) 399 { 400 // Don't handle null info pointer 401 goto HandleDefaultMessage; 402 } 403 } 404 405 switch (uMsg) 406 { 407 case WM_CREATE: 408 { 409 // Get the object pointer from the create param 410 This = (CGridView *)((LPCREATESTRUCT)lParam)->lpCreateParams; 411 412 // Store the pointer in the window's global user data 413 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)This); 414 415 This->OnCreate(hwnd, ((LPCREATESTRUCTW)lParam)->hwndParent); 416 break; 417 } 418 419 case WM_SIZE: 420 { 421 INT Width, Height; 422 Width = LOWORD(lParam); 423 Height = HIWORD(lParam); 424 425 This->OnSize(Width, Height); 426 break; 427 } 428 429 case WM_VSCROLL: 430 { 431 INT Value, Pos; 432 Value = LOWORD(wParam); 433 Pos = HIWORD(wParam); 434 435 This->OnVScroll(Value, Pos); 436 break; 437 } 438 439 case WM_PAINT: 440 { 441 This->OnPaint((HDC)wParam); 442 break; 443 } 444 445 case WM_DESTROY: 446 { 447 This->DeleteCells(); 448 break; 449 } 450 451 default: 452 { 453 HandleDefaultMessage: 454 RetCode = DefWindowProcW(hwnd, uMsg, wParam, lParam); 455 break; 456 } 457 } 458 459 return RetCode; 460 } 461 462 463 void 464 CGridView::DrawGrid( 465 _In_ LPPAINTSTRUCT PaintStruct 466 ) 467 { 468 // Calculate which glyph to start at based on scroll position 469 int i; 470 i = m_xNumCells * m_ScrollPosition; 471 472 // Make sure we have the correct font on the DC 473 HFONT hOldFont; 474 hOldFont = (HFONT)SelectFont(PaintStruct->hdc, 475 m_CurrentFont.hFont); 476 477 // Traverse all the cells 478 for (int y = 0; y < m_yNumCells; y++) 479 for (int x = 0; x < m_xNumCells; x++) 480 { 481 // Update the glyph for this cell 482 WCHAR ch = (WCHAR)m_CurrentFont.ValidGlyphs[i]; 483 m_Cells[y][x]->SetChar(ch); 484 485 // Tell it to paint itself 486 m_Cells[y][x]->OnPaint(*PaintStruct); 487 i++; 488 } 489 490 SelectObject(PaintStruct->hdc, hOldFont); 491 492 } 493 494 void 495 CGridView::DeleteCells() 496 { 497 if (m_Cells == nullptr) 498 return; 499 500 // Free cells withing the 2d array 501 for (int i = 0; i < m_yNumCells; i++) 502 delete[] m_Cells[i]; 503 delete[] m_Cells; 504 505 m_Cells = nullptr; 506 } 507 508 void 509 CGridView::SetCellFocus( 510 _In_ CCell* NewActiveCell 511 ) 512 { 513 if (m_ActiveCell) 514 { 515 // Remove focus from any existing cell 516 m_ActiveCell->SetFocus(false); 517 InvalidateRect(m_hwnd, m_ActiveCell->GetCellCoordinates(), TRUE); 518 } 519 520 // Set the new active cell and give it focus 521 m_ActiveCell = NewActiveCell; 522 m_ActiveCell->SetFocus(true); 523 }