[CLASS2]
[reactos.git] / base / applications / charmap_new / GridView.cpp
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 }