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
CGridView()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
~CGridView()31 CGridView::~CGridView()
32 {
33 }
34
35 bool
Create(_In_ HWND hParent)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
SetFont(_In_ CAtlString & FontName)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
UpdateCellCoordinates()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
OnCreate(_In_ HWND hwnd,_In_ HWND hParent)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
OnSize(_In_ INT Width,_In_ INT Height)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
OnVScroll(_In_ INT Value,_In_ INT Pos)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
OnPaint(_In_opt_ HDC hdc)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
MapWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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
DrawGrid(_In_ LPPAINTSTRUCT PaintStruct)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
DeleteCells()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
SetCellFocus(_In_ CCell * NewActiveCell)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 }
523