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 }