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 INT PrevScrollPosition = m_ScrollPosition; 266 267 switch (Value) 268 { 269 case SB_LINEUP: 270 m_ScrollPosition -= 1; 271 break; 272 273 case SB_LINEDOWN: 274 m_ScrollPosition += 1; 275 break; 276 277 case SB_PAGEUP: 278 m_ScrollPosition -= m_yNumCells; 279 break; 280 281 case SB_PAGEDOWN: 282 m_ScrollPosition += m_yNumCells; 283 break; 284 285 case SB_THUMBTRACK: 286 m_ScrollPosition = Pos; 287 break; 288 289 default: 290 break; 291 } 292 293 // Make sure we don't scroll past row 0 or max rows 294 m_ScrollPosition = max(0, m_ScrollPosition); 295 m_ScrollPosition = min(m_ScrollPosition, m_NumRows); 296 297 // Check if there's a difference from the previous position 298 INT ScrollDiff; 299 ScrollDiff = PrevScrollPosition - m_ScrollPosition; 300 if (ScrollDiff) 301 { 302 // Set the new scrollbar position in the scroll box 303 SetScrollPos(m_hwnd, 304 SB_VERT, 305 m_ScrollPosition, 306 TRUE); 307 308 // Check if the scrollbar has moved more than the 309 // number of visible rows (draged or paged) 310 if (abs(ScrollDiff) < m_yNumCells) 311 { 312 RECT rect; 313 GetClientRect(m_hwnd, &rect); 314 315 // Scroll the visible cells which remain within the grid 316 // and invalidate any new ones which appear from the top / bottom 317 ScrollWindowEx(m_hwnd, 318 0, 319 ScrollDiff * m_CellSize.cy, 320 &rect, 321 &rect, 322 NULL, 323 NULL, 324 SW_INVALIDATE); 325 } 326 else 327 { 328 // All the cells need to be redrawn 329 InvalidateRect(m_hwnd, 330 NULL, 331 TRUE); 332 } 333 } 334 } 335 336 LRESULT 337 CGridView::OnPaint( 338 _In_opt_ HDC hdc 339 ) 340 { 341 PAINTSTRUCT PaintStruct = { 0 }; 342 HDC LocalHdc = NULL; 343 BOOL bSuccess = FALSE; 344 345 // Check if we were passed a DC 346 if (hdc == NULL) 347 { 348 // We weren't, let's get one 349 LocalHdc = BeginPaint(m_hwnd, &PaintStruct); 350 if (LocalHdc) bSuccess = TRUE; 351 } 352 else 353 { 354 // Use the existing DC and just get the region to paint 355 bSuccess = GetUpdateRect(m_hwnd, 356 &PaintStruct.rcPaint, 357 TRUE); 358 if (bSuccess) 359 { 360 // Update the struct with the DC we were passed 361 PaintStruct.hdc = (HDC)hdc; 362 } 363 } 364 365 // Make sure we have a valid DC 366 if (bSuccess) 367 { 368 // Paint the grid and chars 369 DrawGrid(&PaintStruct); 370 371 if (LocalHdc) 372 { 373 EndPaint(m_hwnd, &PaintStruct); 374 } 375 } 376 377 return 0; 378 } 379 380 LRESULT 381 CALLBACK 382 CGridView::MapWndProc( 383 HWND hwnd, 384 UINT uMsg, 385 WPARAM wParam, 386 LPARAM lParam 387 ) 388 { 389 CGridView *This; 390 LRESULT RetCode = 0; 391 392 // Get the object pointer from window context 393 This = (CGridView *)GetWindowLongPtr(hwnd, GWLP_USERDATA); 394 if (This == NULL) 395 { 396 // Check that this isn't a create message 397 if (uMsg != WM_CREATE) 398 { 399 // Don't handle null info pointer 400 goto HandleDefaultMessage; 401 } 402 } 403 404 switch (uMsg) 405 { 406 case WM_CREATE: 407 { 408 // Get the object pointer from the create param 409 This = (CGridView *)((LPCREATESTRUCT)lParam)->lpCreateParams; 410 411 // Store the pointer in the window's global user data 412 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)This); 413 414 This->OnCreate(hwnd, ((LPCREATESTRUCTW)lParam)->hwndParent); 415 break; 416 } 417 418 case WM_SIZE: 419 { 420 INT Width, Height; 421 Width = LOWORD(lParam); 422 Height = HIWORD(lParam); 423 424 This->OnSize(Width, Height); 425 break; 426 } 427 428 case WM_VSCROLL: 429 { 430 INT Value, Pos; 431 Value = LOWORD(wParam); 432 Pos = HIWORD(wParam); 433 434 This->OnVScroll(Value, Pos); 435 break; 436 } 437 438 case WM_PAINT: 439 { 440 This->OnPaint((HDC)wParam); 441 break; 442 } 443 444 case WM_DESTROY: 445 { 446 This->DeleteCells(); 447 break; 448 } 449 450 default: 451 { 452 HandleDefaultMessage: 453 RetCode = DefWindowProcW(hwnd, uMsg, wParam, lParam); 454 break; 455 } 456 } 457 458 return RetCode; 459 } 460 461 462 void 463 CGridView::DrawGrid( 464 _In_ LPPAINTSTRUCT PaintStruct 465 ) 466 { 467 // Calculate which glyph to start at based on scroll position 468 int i; 469 i = m_xNumCells * m_ScrollPosition; 470 471 // Make sure we have the correct font on the DC 472 HFONT hOldFont; 473 hOldFont = (HFONT)SelectFont(PaintStruct->hdc, 474 m_CurrentFont.hFont); 475 476 // Traverse all the cells 477 for (int y = 0; y < m_yNumCells; y++) 478 for (int x = 0; x < m_xNumCells; x++) 479 { 480 // Update the glyph for this cell 481 WCHAR ch = (WCHAR)m_CurrentFont.ValidGlyphs[i]; 482 m_Cells[y][x]->SetChar(ch); 483 484 // Tell it to paint itself 485 m_Cells[y][x]->OnPaint(*PaintStruct); 486 i++; 487 } 488 489 SelectObject(PaintStruct->hdc, hOldFont); 490 491 } 492 493 void 494 CGridView::DeleteCells() 495 { 496 if (m_Cells == nullptr) 497 return; 498 499 // Free cells withing the 2d array 500 for (int i = 0; i < m_yNumCells; i++) 501 delete[] m_Cells[i]; 502 delete[] m_Cells; 503 504 m_Cells = nullptr; 505 } 506 507 void 508 CGridView::SetCellFocus( 509 _In_ CCell* NewActiveCell 510 ) 511 { 512 if (m_ActiveCell) 513 { 514 // Remove focus from any existing cell 515 m_ActiveCell->SetFocus(false); 516 InvalidateRect(m_hwnd, m_ActiveCell->GetCellCoordinates(), TRUE); 517 } 518 519 // Set the new active cell and give it focus 520 m_ActiveCell = NewActiveCell; 521 m_ActiveCell->SetFocus(true); 522 }