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 }